| @@ -0,0 +1,280 @@ | |||
| ############################################################################ | |||
| # David W. Robertson, LBNL | |||
| # See Copyright for copyright notice! | |||
| ########################################################################### | |||
| import sys, types, os.path, pickle | |||
| import StringIO, copy, re | |||
| import unittest | |||
| import ConfigParser | |||
| """ | |||
| utils: | |||
| This module contains utility functions for use by test case modules, a | |||
| class facilitating the use of ConfigParser with multiple test cases, a | |||
| class encapsulating comparisons against a test file, and a test loader | |||
| class with a different loading strategy than the default | |||
| unittest.TestLoader. | |||
| """ | |||
| thisFileName = sys.modules[__name__].__file__ | |||
| class configHandler(ConfigParser.ConfigParser): | |||
| def __init__(self, name="config.py"): | |||
| ConfigParser.ConfigParser.__init__(self) | |||
| # first, look for one in this directory | |||
| try: | |||
| self.read(name) | |||
| except IOError: | |||
| self.read(os.path.dirname(thisFileName) + os.sep + name) | |||
| def getConfigNames(self, section, numMethods, valueFunc=None): | |||
| result = None | |||
| for name, value in self.items(section): | |||
| for i in range(0, numMethods): | |||
| yield value # indicate which test in all cases | |||
| if i == 0: | |||
| result = None | |||
| if valueFunc: | |||
| try: | |||
| result = valueFunc(value) | |||
| except KeyboardInterrupt: | |||
| sys.exit(-1) # for now | |||
| except: # don't care, test will be skipped | |||
| pass | |||
| if valueFunc: | |||
| yield result | |||
| def length(self, section): | |||
| return len(self.options(section)) | |||
| def loadPickledObj(fname): | |||
| """Not currently used. | |||
| """ | |||
| fname = os.path.dirname(thisFileName) + os.sep + fname + ".obj" | |||
| f = open(fname, "r") | |||
| obj = pickle.load(f) | |||
| f.close() | |||
| return obj | |||
| def dumpPickledObj(obj, fname): | |||
| """Not currently used""" | |||
| fname = os.path.dirname(thisFileName) + os.sep + fname + ".obj" | |||
| f = open(fname, "w") | |||
| pickle.dump(obj, f) | |||
| f.close() | |||
| class TestDiff: | |||
| """TestDiff encapsulates comparing a string or StringIO object | |||
| against text in a test file. Test files are expected to | |||
| be located in a subdirectory of the current directory, | |||
| named data (if one doesn't exist, it will be created). | |||
| If used in a test case, this should be instantiated in setUp and | |||
| closed in tearDown. The calling unittest.TestCase instance is passed | |||
| in on object creation. Optional compiled regular expressions | |||
| can also be passed in, which are used to ignore strings | |||
| that one knows in advance will be different, for example | |||
| id="<hex digits>" . | |||
| The initial running of the test will create the test | |||
| files. When the tests are run again, the new output | |||
| is compared against the old, line by line. To generate | |||
| a new test file, remove the old one from data. | |||
| """ | |||
| def __init__(self, testInst, *ignoreList): | |||
| self.dataFile = None | |||
| self.testInst = testInst | |||
| self.origStrFile = None | |||
| # used to divide separate test blocks within the same | |||
| # test file. | |||
| self.divider = "#" + ">" * 75 + "\n" | |||
| self.expectedFailures = copy.copy(ignoreList) | |||
| self.testFilePath = "data" + os.sep | |||
| if not os.path.exists(self.testFilePath): | |||
| os.mkdir(self.testFilePath) | |||
| def setDiffFile(self, fname): | |||
| """setDiffFile attempts to open the test file with the | |||
| given name, and read it into a StringIO instance. | |||
| If the file does not exist, it opens the file for | |||
| writing. | |||
| """ | |||
| filename = fname | |||
| if self.dataFile and not self.dataFile.closed: | |||
| self.dataFile.close() | |||
| try: | |||
| self.dataFile = open(self.testFilePath + filename, "r") | |||
| self.origStrFile = StringIO.StringIO(self.dataFile.read()) | |||
| except IOError: | |||
| try: | |||
| self.dataFile = open(self.testFilePath + filename, "w") | |||
| except IOError: | |||
| print "exception" | |||
| def failUnlessEqual(self, buffer): | |||
| """failUnlessEqual takes either a string or a StringIO | |||
| instance as input, and compares it against the original | |||
| output from the test file. | |||
| """ | |||
| # if not already a string IO | |||
| if not isinstance(buffer, StringIO.StringIO): | |||
| testStrFile = StringIO.StringIO(buffer) | |||
| else: | |||
| testStrFile = buffer | |||
| testStrFile.seek(0) | |||
| if self.dataFile.mode == "r": | |||
| for testLine in testStrFile: | |||
| origLine = self.origStrFile.readline() | |||
| # skip divider | |||
| if origLine == self.divider: | |||
| origLine = self.origStrFile.readline() | |||
| # take out expected failure strings before | |||
| # comparing original against new output | |||
| for cexpr in self.expectedFailures: | |||
| origLine = cexpr.sub('', origLine) | |||
| testLine = cexpr.sub('', testLine) | |||
| self.testInst.failUnlessEqual(origLine, testLine) | |||
| else: # write new test file | |||
| for line in testStrFile: | |||
| self.dataFile.write(line) | |||
| self.dataFile.write(self.divider) | |||
| testStrFile.close() | |||
| def close(self): | |||
| """Closes handle to original test file. | |||
| """ | |||
| if self.dataFile and not self.dataFile.closed: | |||
| self.dataFile.close() | |||
| class MatchTestLoader(unittest.TestLoader): | |||
| """Overrides unittest.TestLoader.loadTestsFromNames to provide a | |||
| simpler and less verbose way to select a subset of tests to run. | |||
| If all tests will always be run, use unittest.TestLoader instead. | |||
| If a top-level test invokes test cases in other scripts, | |||
| MatchTestLoader should be created with topLevel set to True | |||
| to get the correct results. For example, | |||
| def main(): | |||
| loader = utils.MatchTestLoader(True, None, "makeTestSuite") | |||
| unittest.main(defaultTest="makeTestSuite", testLoader=loader) | |||
| The defaultTest argument in the constructor indicates the test to run | |||
| if no additional arguments beyond the test script name are provided. | |||
| """ | |||
| def __init__(self, topLevel, config, defaultTest): | |||
| unittest.TestLoader.__init__(self) | |||
| self.testMethodPrefix = "test" | |||
| self.defaultTest = defaultTest | |||
| self.topLevel = topLevel | |||
| self.config = config | |||
| self.nameGenerator = None | |||
| def loadTestsFromNames(self, unused, module=None): | |||
| """Instead of loading the test names passed into it from | |||
| unittest.TestProgram, loadTestsFromNames uses arguments from the | |||
| command-line instead. This method is necessary because the | |||
| superclass method called from unittest.TestProgram would fail on | |||
| sub-string arguments, and because the default test would | |||
| not be called properly in some cases, if using the argument | |||
| passed in. | |||
| There can be multiple names, both in full or as a substring, on | |||
| the command-line. After getting the arguments, this method | |||
| calls the corresponding method in the superclass. | |||
| """ | |||
| self.testArgs = [] | |||
| if not self.topLevel or (len(sys.argv) != 1): | |||
| for arg in sys.argv[1:]: | |||
| if arg.find("-") != 0: | |||
| self.testArgs.append(arg) | |||
| if not self.testArgs: | |||
| self.testArgs = [None] | |||
| suites = unittest.TestLoader.loadTestsFromNames(self, | |||
| (self.defaultTest,), module) | |||
| return suites | |||
| def loadTestsFromConfig(self, testCaseClass, section, fname="config.py", | |||
| valueFunc=None): | |||
| config = configHandler(fname) | |||
| numTestCases = self.getTestCaseNumber(testCaseClass) | |||
| self.nameGenerator = config.getConfigNames(section, | |||
| numTestCases, valueFunc) | |||
| configLen = config.length(section) | |||
| suite = unittest.TestSuite() | |||
| suite.addTest(self.loadTestsFromTestCase(testCaseClass)) | |||
| for i in range(1, configLen): | |||
| suite.addTest(self.loadTestsFromTestCase(testCaseClass)) | |||
| return suite | |||
| def getTestCaseNumber(self, testCaseClass): | |||
| """looks for any test methods whose name contains testStr, checking | |||
| if a test method has already been added. If there is not a match, | |||
| it checks for an exact match with the test case name, and | |||
| returns the number of test cases. | |||
| """ | |||
| methods = self.getTestCaseNames(testCaseClass) | |||
| prevAdded = [] | |||
| counter = 0 | |||
| for testStr in self.testArgs: | |||
| # print "test argument ", testStr | |||
| if testStr: | |||
| for m in methods: | |||
| if m.find(testStr) >= 0 and m not in prevAdded: | |||
| counter = counter + 1 | |||
| prevAdded.append(m) | |||
| if counter: | |||
| return counter | |||
| if (not testStr) or (testCaseClass.__name__ == testStr): | |||
| for m in methods: | |||
| counter = counter + 1 | |||
| prevAdded.append(m) | |||
| # print "found %d cases" % counter | |||
| return counter | |||
| def loadTestsFromTestCase(self, testCaseClass): | |||
| """looks for any test methods whose name contains testStr, checking | |||
| if a test method has already been added. If there is not a match, | |||
| it checks for an exact match with the test case name, and loads | |||
| all methods if so. | |||
| """ | |||
| methods = self.getTestCaseNames(testCaseClass) | |||
| prevAdded = [] | |||
| suites = unittest.TestSuite() | |||
| for testStr in self.testArgs: | |||
| # print testStr | |||
| if testStr: | |||
| for m in methods: | |||
| if m.find(testStr) >= 0 and m not in prevAdded: | |||
| suites.addTest(testCaseClass(m)) | |||
| prevAdded.append(m) | |||
| if suites.countTestCases(): | |||
| return suites | |||
| for testStr in self.testArgs: | |||
| if (not testStr) or (testCaseClass.__name__ == testStr): | |||
| for m in methods: | |||
| suites.addTest(testCaseClass(m)) | |||
| prevAdded.append(m) | |||
| if suites.countTestCases(): | |||
| return suites | |||
| return suites | |||