PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/unittests/runUnitTests.py

https://github.com/mogaal/editra
Python | 354 lines | 340 code | 4 blank | 10 comment | 3 complexity | e8bf25d9bb47054973c4944cc0da042f MD5 | raw file
  1. ################################################################################
  2. ## Name: runUnitTests.py #
  3. ## Purpose: Run the Unit Test Suite #
  4. ## Author: Cody Precord <cprecord@editra.org> #
  5. ## Copyright: (c) 2008 Cody Precord <staff@editra.org> #
  6. ## License: wxWindows License #
  7. ################################################################################
  8. """
  9. Run Editra's Unittest Suite.
  10. This module is mostly copied from the wxPython Unittest Suite.
  11. @summary: Unittest Suite Main Module
  12. """
  13. __author__ = "Cody Precord <cprecord@editra.org>"
  14. __svnid__ = "$Id: runUnitTests.py 65165 2010-08-02 22:04:37Z CJP $"
  15. __revision__ = "$Revision: 65165 $"
  16. #-----------------------------------------------------------------------------#
  17. # Imports
  18. import os
  19. import sys
  20. import unittest
  21. import time
  22. import wx
  23. from optparse import OptionParser
  24. # Put Editra/src on the path
  25. sys.path.append(os.path.abspath("../../src"))
  26. sys.path.append(os.path.abspath("../../src/extern"))
  27. # Local Utilities
  28. import common
  29. # ----------------- Helper Functions / Classes ---------------------
  30. # TODO: maybe change some variable names?
  31. # TODO: maybe put this function as a method somewhere else?
  32. def _make_clean_opt_string():
  33. # which options was this run called with?
  34. # replace short opts with long opts (explicit is better than implicit)
  35. opt_string = ""
  36. args = sys.argv[1:]
  37. for arg in args:
  38. if arg.startswith("-") and not arg.startswith("--"):
  39. # handle the case where opt and arg are conjoined
  40. arg2 = None
  41. if len(arg) > 2:
  42. arg2 = arg[2:]
  43. arg = arg[:2]
  44. # it's a short opt, now find it
  45. for opt in parser.option_list:
  46. if arg in opt._short_opts:
  47. opt_string += opt._long_opts[0]
  48. if opt.action == "store":
  49. opt_string += "="
  50. if arg2 != None:
  51. opt_string += arg2
  52. else:
  53. opt_string += " "
  54. else:
  55. opt_string += arg
  56. opt_string += " "
  57. if opt_string == "":
  58. opt_string = "NONE"
  59. return opt_string
  60. def output(string):
  61. print string
  62. #-----------------------------------------------------------------------------#
  63. class UnitTestSuite:
  64. def __init__(self, include="", exclude="", tests=""):
  65. # error checking
  66. if include != "" and exclude != "":
  67. raise ValueError("include and exclude arguments are mutually exclusive")
  68. # TODO: could this become a simple os.listdir(".")?
  69. _rootdir = os.path.abspath(sys.path[0])
  70. if not os.path.isdir(_rootdir):
  71. _rootdir = os.path.dirname(_rootdir)
  72. self.rootdir = _rootdir # to come in handy later
  73. # a dict of all possible test modules that could be run
  74. # ASSUME: each module name is unique not solely because of case
  75. _module_names = {}
  76. for _name in [ n[:-3] for n in os.listdir(self.rootdir)
  77. if n.startswith("test") and n.endswith(".py") ]:
  78. _module_names[ _name.lower() ] = _name
  79. # make the include/exclude/tests lists
  80. _module_specs = None
  81. _spec_type = None
  82. _test_specs = None
  83. if include != "":
  84. _module_specs = self._clean_listify(include)
  85. _spec_type = "include"
  86. elif exclude != "":
  87. _module_specs = self._clean_listify(exclude)
  88. _spec_type = "exclude"
  89. if tests != "":
  90. _test_specs = self._clean_listify(tests, False)
  91. # make sure they all exist
  92. if _module_specs != None: # TODO: got to be a better place to put this
  93. for _mod in _module_specs:
  94. if not _module_names.has_key(_mod.lower()):
  95. parser.error("Module %s not found under test" % (_mod))
  96. # now import the modules
  97. if _module_specs == None:
  98. self.modules = [ __import__(name) for name in _module_names.values() ]
  99. elif _spec_type == "include":
  100. self.modules = [ __import__(name) for name in _module_specs ]
  101. elif _spec_type == "exclude":
  102. self.modules = [ __import__(name) for name in _module_names.values()
  103. if name not in _module_specs ]
  104. # convert modules into suites
  105. self.suites = []
  106. for module in self.modules:
  107. _classname = module.__name__[4:] + "Test"
  108. _class = module.__getattribute__(_classname)
  109. # build test suite (whether or not --tests are specified)
  110. if _test_specs == None:
  111. _suite = unittest.makeSuite(_class)
  112. else:
  113. _suite = unittest.TestSuite()
  114. for _test_name in unittest.getTestCaseNames(_class,"test"):
  115. for _test in _test_specs:
  116. _docstr = getattr(_class, _test_name).__doc__
  117. if _test_name.lower().find(_test.lower()) != -1 or \
  118. _docstr != None and _docstr.lower().find(_test.lower()) != -1:
  119. _suite.addTest(_class(_test_name))
  120. break
  121. # filter out tests that shouldn't be run in subclasses
  122. _tests = _suite._tests
  123. for _t in _tests:
  124. # TODO: pull logic into wxtest
  125. # or use the version of unittest instead
  126. if sys.version_info[0:2] >= (2,5):
  127. _mname = _t._testMethodName
  128. else:
  129. _mname = _t._TestCase__testMethodName
  130. if _mname.find('_wx') != -1:
  131. # grab the class: everything between '_wx' and 'Only' at the end
  132. restriction = _mname[_mname.find('_wx')+3:-4]
  133. if not _class.__name__.startswith(restriction):
  134. #print "filtered: %s (class=%s)" % (mname,_class.__name__)
  135. _tests.remove(_t)
  136. # if suite is non-empty...
  137. if _suite.countTestCases() > 0:
  138. # add it to the list of suites :-)
  139. self.suites.append(_suite)
  140. def _clean_listify(self, string, include_or_exclude=True):
  141. _clean_list = []
  142. _list = string.split(",")
  143. for s in _list:
  144. if include_or_exclude:
  145. if s.endswith(".py"):
  146. s = s[:-3]
  147. if s.startswith("wx."):
  148. s = "test" + s[3:]
  149. if not s.startswith("test"):
  150. s = "test" + s
  151. _clean_list.append(s)
  152. # maintains capitalization
  153. return _clean_list
  154. def _start_figleaf(self):
  155. if options.figleaf != "":
  156. globals()["figleaf"] = __import__("figleaf")
  157. # TODO: perhaps make this class-specific rather than global?
  158. globals()["figfile"] = os.path.join(self.rootdir, options.figleaf_filename)
  159. if os.path.exists(figfile):
  160. os.remove(figfile)
  161. figleaf.start(ignore_python_lib=False)
  162. def _stop_figleaf(self):
  163. if options.figleaf != "":
  164. figleaf.stop()
  165. figleaf.write_coverage(figfile)
  166. def run(self):
  167. test_run_data = UnitTestRunData()
  168. self._start_figleaf()
  169. self.start_time = time.time()
  170. # run tests
  171. for _suite in self.suites:
  172. _result = unittest.TestResult()
  173. _suite.run(_result)
  174. _module_name = _suite._tests[0].__module__
  175. test_run_data.addResult(_module_name, _result)
  176. self.stop_time = time.time()
  177. self._stop_figleaf()
  178. # process results
  179. test_run_data.setTime(self.start_time, self.stop_time)
  180. test_run_data.process()
  181. # return results
  182. return test_run_data
  183. #-----------------------------------------------------------------------------#
  184. class UnitTestRunData:
  185. def __init__(self):
  186. self.results = {}
  187. def addResult(self, module_name, result):
  188. self.results[module_name] = result
  189. def setTime(self, start, stop):
  190. self.startTime = start
  191. self.stopTime = stop
  192. def process(self):
  193. # process data
  194. self.elapsedTime = self.stopTime - self.startTime
  195. self.countSuites = len(self.results)
  196. self.countSuccesses = 0
  197. self.countFailures = 0
  198. self.countErrors = 0
  199. self.rawData = {}
  200. for _module_name, _result in self.results.iteritems():
  201. # TODO: revisit all this processing, is everything necessary?
  202. tmp = {}
  203. # parse results individually
  204. tmp["failures"] = len(_result.failures)
  205. tmp["errors"] = len(_result.errors)
  206. tmp["successes"] = _result.testsRun - tmp["failures"] - tmp["errors"]
  207. # total results
  208. self.countSuccesses += tmp["successes"]
  209. self.countFailures += tmp["failures"]
  210. self.countErrors += tmp["errors"]
  211. # TODO: add processing here
  212. tmp["failure_data"] = _result.failures
  213. tmp["error_data"] = _result.errors
  214. self.rawData[_module_name] = tmp
  215. # -----------------------------------------------------------
  216. # -------------------- Option Logic -------------------------
  217. # Options
  218. usage = "usage: python %prog [options]"
  219. parser = OptionParser(usage=usage)
  220. parser.add_option("-o", "--output-filename", default="",
  221. action="store", dest="outfilename",
  222. metavar="FILE",
  223. help="redirect output from console to FILE")
  224. parser.add_option("-f", "--figleaf", default="",
  225. action="store", dest="figleaf", metavar="FILE",
  226. help="use the figleaf code-coverage tool, and write figleaf output to " +
  227. "FILE. you must have figleaf installed to use this option. " +
  228. "using this option will result in a slower test run")
  229. parser.add_option("-i", "--include-modules", default="",
  230. action="store", dest="module_list",
  231. help="run only the comma-separated list of modules given. use either " +
  232. "wx class names or the name of the desired test module. " +
  233. "don't use spaces in the list")
  234. parser.add_option("-e", "--exclude-modules", default="",
  235. action="store", dest="module_ex_list",
  236. help="run all modules excluding those given in the comma-separated " +
  237. "list given. use either wx class names or the name of the desired " +
  238. "test module.")
  239. parser.add_option("-t", "--tests", default="",
  240. action="store", dest="test_list",
  241. help="run only a targeted list of tests. give a comma-separated list " +
  242. "of strings, and each test whose name or docstring contains " +
  243. "one of those given will be run.")
  244. def runUnitTestsAndOutputResults():
  245. unit_test_suite = UnitTestSuite(include=options.module_list,
  246. exclude=options.module_ex_list,
  247. tests=options.test_list)
  248. result_data = unit_test_suite.run()
  249. # see refactored method above
  250. opt_string = _make_clean_opt_string()
  251. # -----------------------------------------------------------
  252. # ------------------- Output Reporting ----------------------
  253. output("") # make things easier to read
  254. output("%s - %s\n" % (time.asctime(), wx.GetOsDescription()))
  255. output("Platform Information")
  256. output("Platform [sys.platform]: %s" % sys.platform)
  257. output("Python Version [sys.version]: %s" % sys.version)
  258. output("wx Version [wx.version()]: %s" % wx.version())
  259. output("OS [wx.GetOsDescription()]: %s" % wx.GetOsDescription())
  260. output("wx Info [wx.PlatformInfo]: %s" % str(wx.PlatformInfo))
  261. output("runUnitTests.py options: %s" % opt_string)
  262. output("\n----------------------\n")
  263. output("Summary")
  264. output("Run completed in %.2f seconds" % (result_data.elapsedTime))
  265. output("%d classes tested" % (result_data.countSuites))
  266. output("%d tests passed in total!" % (result_data.countSuccesses))
  267. if result_data.countFailures > 0:
  268. output("%d tests failed in total!" % (result_data.countFailures))
  269. if result_data.countErrors > 0:
  270. output("%d tests erred in total!" % (result_data.countErrors))
  271. output("\n----------------------\n")
  272. data_items = result_data.rawData.items()
  273. data_items.sort()
  274. output("Module Data")
  275. for mod_name, results in data_items:
  276. messages = ["%d passed" % (results["successes"])]
  277. if results["failures"] > 0:
  278. messages.append("%d failed" % (results["failures"]))
  279. if results["errors"] > 0:
  280. messages.append("%d erred" % (results["errors"]))
  281. output("%s: %s" % (mod_name, ", ".join(messages)))
  282. output("\n----------------------\n")
  283. if result_data.countFailures + result_data.countErrors > 0:
  284. output("Failure Data")
  285. for mod_name, results in data_items:
  286. # report on it
  287. for failure in results["failure_data"] + results["error_data"]:
  288. type = None
  289. if failure in results["failure_data"]:
  290. type = "Fail: "
  291. elif failure in results["error_data"]:
  292. type = "Error: "
  293. output(" " + type + str(failure[0]))
  294. output(" " + str(failure[1]).replace("\n","\n "))
  295. #-----------------------------------------------------------------------------#
  296. if __name__ == '__main__':
  297. (options, args) = parser.parse_args()
  298. # Options error-checking
  299. if options.module_list != "" and options.module_ex_list != "":
  300. parser.error("options --exclude-modules and --include-modules are mutually exclusive")
  301. # File redirect
  302. if options.outfilename != "":
  303. origstdout = sys.stdout
  304. try:
  305. sys.stdout = open(options.outfilename,'w')
  306. except IOError:
  307. print "Error opening output file, defaulting to original stdout"
  308. sys.stdout = origstdout
  309. app = common.EdApp(False)
  310. runUnitTestsAndOutputResults()
  311. # app.MainLoop()