You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

281 lines
10 KiB

  1. ############################################################################
  2. # David W. Robertson, LBNL
  3. # See Copyright for copyright notice!
  4. ###########################################################################
  5. import sys, types, os.path, pickle
  6. import StringIO, copy, re
  7. import unittest
  8. import ConfigParser
  9. """
  10. utils:
  11. This module contains utility functions for use by test case modules, a
  12. class facilitating the use of ConfigParser with multiple test cases, a
  13. class encapsulating comparisons against a test file, and a test loader
  14. class with a different loading strategy than the default
  15. unittest.TestLoader.
  16. """
  17. thisFileName = sys.modules[__name__].__file__
  18. class configHandler(ConfigParser.ConfigParser):
  19. def __init__(self, name="config.py"):
  20. ConfigParser.ConfigParser.__init__(self)
  21. # first, look for one in this directory
  22. try:
  23. self.read(name)
  24. except IOError:
  25. self.read(os.path.dirname(thisFileName) + os.sep + name)
  26. def getConfigNames(self, section, numMethods, valueFunc=None):
  27. result = None
  28. for name, value in self.items(section):
  29. for i in range(0, numMethods):
  30. yield value # indicate which test in all cases
  31. if i == 0:
  32. result = None
  33. if valueFunc:
  34. try:
  35. result = valueFunc(value)
  36. except KeyboardInterrupt:
  37. sys.exit(-1) # for now
  38. except: # don't care, test will be skipped
  39. pass
  40. if valueFunc:
  41. yield result
  42. def length(self, section):
  43. return len(self.options(section))
  44. def loadPickledObj(fname):
  45. """Not currently used.
  46. """
  47. fname = os.path.dirname(thisFileName) + os.sep + fname + ".obj"
  48. f = open(fname, "r")
  49. obj = pickle.load(f)
  50. f.close()
  51. return obj
  52. def dumpPickledObj(obj, fname):
  53. """Not currently used"""
  54. fname = os.path.dirname(thisFileName) + os.sep + fname + ".obj"
  55. f = open(fname, "w")
  56. pickle.dump(obj, f)
  57. f.close()
  58. class TestDiff:
  59. """TestDiff encapsulates comparing a string or StringIO object
  60. against text in a test file. Test files are expected to
  61. be located in a subdirectory of the current directory,
  62. named data (if one doesn't exist, it will be created).
  63. If used in a test case, this should be instantiated in setUp and
  64. closed in tearDown. The calling unittest.TestCase instance is passed
  65. in on object creation. Optional compiled regular expressions
  66. can also be passed in, which are used to ignore strings
  67. that one knows in advance will be different, for example
  68. id="<hex digits>" .
  69. The initial running of the test will create the test
  70. files. When the tests are run again, the new output
  71. is compared against the old, line by line. To generate
  72. a new test file, remove the old one from data.
  73. """
  74. def __init__(self, testInst, *ignoreList):
  75. self.dataFile = None
  76. self.testInst = testInst
  77. self.origStrFile = None
  78. # used to divide separate test blocks within the same
  79. # test file.
  80. self.divider = "#" + ">" * 75 + "\n"
  81. self.expectedFailures = copy.copy(ignoreList)
  82. self.testFilePath = "data" + os.sep
  83. if not os.path.exists(self.testFilePath):
  84. os.mkdir(self.testFilePath)
  85. def setDiffFile(self, fname):
  86. """setDiffFile attempts to open the test file with the
  87. given name, and read it into a StringIO instance.
  88. If the file does not exist, it opens the file for
  89. writing.
  90. """
  91. filename = fname
  92. if self.dataFile and not self.dataFile.closed:
  93. self.dataFile.close()
  94. try:
  95. self.dataFile = open(self.testFilePath + filename, "r")
  96. self.origStrFile = StringIO.StringIO(self.dataFile.read())
  97. except IOError:
  98. try:
  99. self.dataFile = open(self.testFilePath + filename, "w")
  100. except IOError:
  101. print "exception"
  102. def failUnlessEqual(self, buffer):
  103. """failUnlessEqual takes either a string or a StringIO
  104. instance as input, and compares it against the original
  105. output from the test file.
  106. """
  107. # if not already a string IO
  108. if not isinstance(buffer, StringIO.StringIO):
  109. testStrFile = StringIO.StringIO(buffer)
  110. else:
  111. testStrFile = buffer
  112. testStrFile.seek(0)
  113. if self.dataFile.mode == "r":
  114. for testLine in testStrFile:
  115. origLine = self.origStrFile.readline()
  116. # skip divider
  117. if origLine == self.divider:
  118. origLine = self.origStrFile.readline()
  119. # take out expected failure strings before
  120. # comparing original against new output
  121. for cexpr in self.expectedFailures:
  122. origLine = cexpr.sub('', origLine)
  123. testLine = cexpr.sub('', testLine)
  124. self.testInst.failUnlessEqual(origLine, testLine)
  125. else: # write new test file
  126. for line in testStrFile:
  127. self.dataFile.write(line)
  128. self.dataFile.write(self.divider)
  129. testStrFile.close()
  130. def close(self):
  131. """Closes handle to original test file.
  132. """
  133. if self.dataFile and not self.dataFile.closed:
  134. self.dataFile.close()
  135. class MatchTestLoader(unittest.TestLoader):
  136. """Overrides unittest.TestLoader.loadTestsFromNames to provide a
  137. simpler and less verbose way to select a subset of tests to run.
  138. If all tests will always be run, use unittest.TestLoader instead.
  139. If a top-level test invokes test cases in other scripts,
  140. MatchTestLoader should be created with topLevel set to True
  141. to get the correct results. For example,
  142. def main():
  143. loader = utils.MatchTestLoader(True, None, "makeTestSuite")
  144. unittest.main(defaultTest="makeTestSuite", testLoader=loader)
  145. The defaultTest argument in the constructor indicates the test to run
  146. if no additional arguments beyond the test script name are provided.
  147. """
  148. def __init__(self, topLevel, config, defaultTest):
  149. unittest.TestLoader.__init__(self)
  150. self.testMethodPrefix = "test"
  151. self.defaultTest = defaultTest
  152. self.topLevel = topLevel
  153. self.config = config
  154. self.nameGenerator = None
  155. def loadTestsFromNames(self, unused, module=None):
  156. """Instead of loading the test names passed into it from
  157. unittest.TestProgram, loadTestsFromNames uses arguments from the
  158. command-line instead. This method is necessary because the
  159. superclass method called from unittest.TestProgram would fail on
  160. sub-string arguments, and because the default test would
  161. not be called properly in some cases, if using the argument
  162. passed in.
  163. There can be multiple names, both in full or as a substring, on
  164. the command-line. After getting the arguments, this method
  165. calls the corresponding method in the superclass.
  166. """
  167. self.testArgs = []
  168. if not self.topLevel or (len(sys.argv) != 1):
  169. for arg in sys.argv[1:]:
  170. if arg.find("-") != 0:
  171. self.testArgs.append(arg)
  172. if not self.testArgs:
  173. self.testArgs = [None]
  174. suites = unittest.TestLoader.loadTestsFromNames(self,
  175. (self.defaultTest,), module)
  176. return suites
  177. def loadTestsFromConfig(self, testCaseClass, section, fname="config.py",
  178. valueFunc=None):
  179. config = configHandler(fname)
  180. numTestCases = self.getTestCaseNumber(testCaseClass)
  181. self.nameGenerator = config.getConfigNames(section,
  182. numTestCases, valueFunc)
  183. configLen = config.length(section)
  184. suite = unittest.TestSuite()
  185. suite.addTest(self.loadTestsFromTestCase(testCaseClass))
  186. for i in range(1, configLen):
  187. suite.addTest(self.loadTestsFromTestCase(testCaseClass))
  188. return suite
  189. def getTestCaseNumber(self, testCaseClass):
  190. """looks for any test methods whose name contains testStr, checking
  191. if a test method has already been added. If there is not a match,
  192. it checks for an exact match with the test case name, and
  193. returns the number of test cases.
  194. """
  195. methods = self.getTestCaseNames(testCaseClass)
  196. prevAdded = []
  197. counter = 0
  198. for testStr in self.testArgs:
  199. # print "test argument ", testStr
  200. if testStr:
  201. for m in methods:
  202. if m.find(testStr) >= 0 and m not in prevAdded:
  203. counter = counter + 1
  204. prevAdded.append(m)
  205. if counter:
  206. return counter
  207. if (not testStr) or (testCaseClass.__name__ == testStr):
  208. for m in methods:
  209. counter = counter + 1
  210. prevAdded.append(m)
  211. # print "found %d cases" % counter
  212. return counter
  213. def loadTestsFromTestCase(self, testCaseClass):
  214. """looks for any test methods whose name contains testStr, checking
  215. if a test method has already been added. If there is not a match,
  216. it checks for an exact match with the test case name, and loads
  217. all methods if so.
  218. """
  219. methods = self.getTestCaseNames(testCaseClass)
  220. prevAdded = []
  221. suites = unittest.TestSuite()
  222. for testStr in self.testArgs:
  223. # print testStr
  224. if testStr:
  225. for m in methods:
  226. if m.find(testStr) >= 0 and m not in prevAdded:
  227. suites.addTest(testCaseClass(m))
  228. prevAdded.append(m)
  229. if suites.countTestCases():
  230. return suites
  231. for testStr in self.testArgs:
  232. if (not testStr) or (testCaseClass.__name__ == testStr):
  233. for m in methods:
  234. suites.addTest(testCaseClass(m))
  235. prevAdded.append(m)
  236. if suites.countTestCases():
  237. return suites
  238. return suites