/4Suite-XML-1.0.2/Ft/Lib/TestSuite/Tester.py
# · Python · 493 lines · 440 code · 27 blank · 26 comment · 19 complexity · 427c7910bf0ca9b7cd60d3961562b8b6 MD5 · raw file
- ########################################################################
- # $Header: /var/local/cvsroot/4Suite/Ft/Lib/TestSuite/Tester.py,v 1.13 2004/11/04 05:33:46 mbrown Exp $
- """
- Provides the Tester class, which is the hub for all testing.
- Copyright 2004 Fourthought, Inc. (USA).
- Detailed license and copyright information: http://4suite.org/COPYRIGHT
- Project home, documentation, distributions: http://4suite.org/
- """
- import sys, os, time
- import traceback, linecache
- from distutils import spawn
- from sys import _getframe
- from Ft.Lib import number, Terminal
- from Ft.Lib.Terminal import AnsiEscapes
- ###########################################
- #
- # Verbosity Levels
- # 4 print out group headers, all test names and test results and debug messges
- # 3 print out group headers, all test names and test results
- # 2 print out group headers and errors and warnings
- # 1 print out group headers and errors
- # 0 Nothing
- VERBOSE_DEBUG = 4
- VERBOSE_MSG = 3
- VERBOSE_WARN = 2
- VERBOSE_ERROR = 1
- VERBOSE_OFF = 0
- SHOW_TESTS = VERBOSE_MSG
- SHOW_GROUPS = VERBOSE_ERROR
- _group_headers = ['#', '*', '=', ':', '%', '+', '-','@','$','&']
- def _frame_lineno(frame):
- """
- Calculate correct line number of stack frame given in frame.
- """
- code = frame.f_code
- if not hasattr(code, 'co_lnotab'):
- return frame.f_lineno
- tab = code.co_lnotab
- line = code.co_firstlineno
- stopat = frame.f_lasti
- addr = 0
- for i in range(0, len(tab), 2):
- addr = addr + ord(tab[i])
- if addr > stopat:
- break
- line = line + ord(tab[i+1])
- return line
- def extract_stack(frame=None, limit=None):
- if frame is None:
- # Skip this function body
- frame = _getframe(1)
- if limit is None:
- limit = getattr(sys, 'tracebacklimit', 1000)
- stack = []
- while frame and limit > 0:
- lineno = _frame_lineno(frame)
- co = frame.f_code
- filename = co.co_filename
- name = co.co_name
- line = linecache.getline(filename, lineno)
- if line:
- line = line.strip()
- else:
- line = None
- stack.append((filename, lineno, name, line))
- frame = frame.f_back
- limit -= 1
- stack.reverse()
- return stack
- def format_stack(frame=None, limit=None):
- """Shorthand for 'format_list(extract_stack(f, limit))'."""
- return traceback.format_list(extract_stack(frame, limit))
- class TestItem:
- def __init__(self, title):
- self.title = title
- self.messages = []
- self.hasErrors = 0
- self.hasWarnings = 0
- self.comparisons = 0
- self.compareTime = 0.0
- self.totalTime = 0.0
- self.runTime = 0.0
- self.startTime = time.time()
- return
- def __repr__(self):
- return '<%s, title=%r, compares=%d>' % (self.__class__.__name__,
- self.title,
- self.comparisons)
- def debug(self, msg):
- if msg:
- self.messages.append((VERBOSE_DEBUG, msg))
- return
- def message(self, msg):
- if msg:
- self.messages.append((VERBOSE_MSG, msg))
- return
- def warning(self, msg):
- self.hasWarnings = 1
- if msg:
- self.messages.append((VERBOSE_MSG, msg))
- return
- def error(self, msg):
- self.hasErrors = 1
- if msg:
- self.messages.append((VERBOSE_ERROR, msg))
- return
- def finish(self):
- self.totalTime = time.time() - self.startTime
- self.runTime = self.totalTime - self.compareTime
- return
- class Tester:
- double_sep = '=' * 72
- single_sep = '-' * 72
- NORMAL = AnsiEscapes.Colors.DEFAULT
- GRAY = AnsiEscapes.Colors.FOREGROUND_GRAY
- RED = AnsiEscapes.Colors.FOREGROUND_RED
- GREEN = AnsiEscapes.Colors.FOREGROUND_LIME
- BROWN = AnsiEscapes.Colors.FOREGROUND_BROWN
- YELLOW = AnsiEscapes.Colors.FOREGROUND_YELLOW
- WHITE = AnsiEscapes.Colors.FOREGROUND_WHITE
- def __init__(self, stopOnError=1, useColor=1, verbose=VERBOSE_DEBUG,
- stream=sys.stdout):
- self.stopOnError = stopOnError
- self.verbose = verbose
- self.stream = stream
- self.tty = Terminal.Terminal(stream, keepAnsiEscapes=useColor)
- self._writetty = self.tty.writetty
- self.test_data = {}
- self.groups = []
- self.test = None
- self.testTime = 0.0
- self._diffCommand = spawn.find_executable('diff')
- self._compareCtr = 0
- # Reporting information
- self.warnings = []
- self.failures = []
- self.exceptions = []
- self.totalGroups = 0
- self.totalTests = 0
- self.totalComparisons = 0
- return
- ### Testing Methods ###
- def startGroup(self, title):
- header = _group_headers[len(self.groups)]*10
- self.writeline(SHOW_GROUPS, '%s %s %s' % (header, title, header))
- self.groups.append(title)
- self.totalGroups += 1
- return
- def groupDone(self):
- if self.groups:
- del self.groups[-1]
- else:
- self.warning('groupDone called without active group')
- return
- def startTest(self, title):
- if self.test:
- # Add warning to current test
- self.warning('testDone not called')
- self.testDone()
- self.test = TestItem(title)
- # Add the warning to the new test as well
- self.warning('startTest called with active test')
- else:
- self.test = TestItem(title)
- self.totalTests += 1
- return
- def testDone(self):
- if not self.test:
- self.warning('testDone called without active test')
- return
- self.test.finish()
- if self.test.hasErrors:
- status = '[%sFAILED%s]' % (self.RED,
- self.NORMAL)
- level = VERBOSE_ERROR
- elif self.test.hasWarnings:
- status = '[%s WARN %s]' % (self.YELLOW,
- self.NORMAL)
- level = VERBOSE_WARN
- else:
- status = '[%s OK %s]' % (self.GREEN,
- self.NORMAL)
- level = SHOW_TESTS
- if not self.test.comparisons:
- title = '%s (%0.3f secs)' % (self.test.title,
- self.test.totalTime)
- else:
- title = '%s (%0.3f run, %0.3f total)' % (self.test.title,
- self.test.runTime,
- self.test.totalTime)
- spaces = self.tty.columns() - 9 # length of status
- if len(title) > spaces:
- self.writeline(level, title)
- self.writeline(level, ' '*spaces + status)
- else:
- self.writeline(level, title.ljust(spaces) + status)
- # Write the messages in the queue
- for level, line in self.test.messages:
- self.writeline(level, line)
- self.testTime += self.test.totalTime
- self.test = None
- return
- # -- result testing ----------------------------------------------
- def testException(self, func, args, etype, value={}, stackLevel=1, kwargs={}):
- # Increase stack trimming to include this function call
- stackLevel = stackLevel + 1
- try:
- func(*args, **kwargs)
- except etype, e:
- for attr, expected in value.items():
- if hasattr(e, attr):
- self.compare(expected, getattr(e, attr),
- 'exception attribute %s' % repr(attr),
- stackLevel=stackLevel)
- except:
- self.exception("Wrong exception raised")
- else:
- self.error("Expected exception '%s' not raised" % etype,
- stackLevel=stackLevel)
- return
- def compare(self, expected, actual, msg=None, func=cmp, diff=0, stackLevel=1, funcArgs={}):
- """
- Uses func to compare the expected result with actual result
- of a regression test.
- diff is ignored.
- msg is an optional custom message to print if the
- comparison tests positive (i.e. the results differ).
- func is the comparison function to use, and must be a
- function that returns the same as the built-in cmp().
- stackLevel affects exception reporting.
- funcArgs is an optional dictionary of keyword arguments that
- will be passed to the comparison function, if the dictionary
- is not empty.
- """
- self.totalComparisons += 1
- # Normalize float values
- if type(expected) == type(actual) == float:
- if number.finite(expected):
- expected = float(str(expected))
- elif number.isnan(expected):
- expected = 'NaN'
- elif number.isinf(expected) > 0:
- expected = 'Inf'
- else:
- expected = '-Inf'
- if number.finite(actual):
- actual = float(str(actual))
- elif number.isnan(actual):
- actual = 'NaN'
- elif number.isinf(actual) > 0:
- actual = 'Inf'
- else:
- actual = '-Inf'
- # Make sure there was a message for this comparison
- if not msg:
- if self.test:
- self.test.comparisons += 1
- msg = 'Test %d' % (self.test.comparisons)
- else:
- msg = 'Test %d of all tests' % self.totalComparisons
- start = time.time()
- try:
- if funcArgs:
- res = func(expected, actual, **funcArgs)
- else:
- res = func(expected, actual)
- if res:
- # failure
- self.message(msg)
- if diff and self.verbose >= VERBOSE_DEBUG:
- self._diff(expected, actual)
- error = '%sExpected:%s %s\n' % (self.GREEN,
- self.NORMAL,
- repr(expected))
- error += '%sCompared:%s %s' % (self.RED,
- self.NORMAL,
- repr(actual))
- self.error(error, stackLevel=(stackLevel+1))
- return 0
- finally:
- end = time.time()
- if self.test:
- self.test.compareTime += (end - start)
- # success
- return 1
- def compareIn(self, expected, actual, msg=None, stackLevel=1):
- """Test that 'actual' is in 'expected'"""
- func = lambda expected, actual: actual not in expected
- return self.compare(expected, actual, msg, func,
- stackLevel=(stackLevel+1))
- # -- display functions -------------------------------------------
- def writeline(self, level, msg):
- if self.verbose >= level:
- self._writetty(msg)
- self._writetty('\n')
- return
- def debug(self, msg):
- """debug-level messages"""
- if self.test:
- self.test.debug(msg)
- else:
- self.writeline(VERBOSE_DEBUG, msg)
- return
- def message(self, msg):
- """informational"""
- if self.test:
- self.test.message(msg)
- else:
- self.writeline(VERBOSE_MSG, msg)
- return
- def warning(self, msg):
- """warning conditions"""
- titles = self.groups[:]
- if self.test:
- titles.append(self.test.title)
- self.test.warning(msg)
- else:
- self.writeline(VERBOSE_WARN, msg)
- self.warnings.append((titles, msg))
- return
- def error(self, msg, traceLimit=1, stackLevel=1):
- """error conditions"""
- if self.stopOnError:
- traceLimit = getattr(sys, 'tracebacklimit', 1000)
- # Trim off 'stackLevel' items from the frame stack.
- frame = _getframe(stackLevel)
- # Format only 'traceLimit' items in the stack
- lines = format_stack(frame, traceLimit)
- msg += '\n' + ''.join(lines)
- titles = self.groups[:]
- if self.test:
- titles.append(self.test.title)
- self.test.error(msg)
- else:
- self.writeline(VERBOSE_ERROR, msg)
- self.failures.append((titles, msg))
- if self.stopOnError:
- self.testDone()
- raise SystemExit(1)
- return
- def exception(self, msg):
- """system is unusable""" # ??? looks like it's being used anyway
- if not sys.exc_info()[2]:
- raise AttributeError('No exception; use error method instead')
- try:
- etype, value, tb = sys.exc_info()
- lines = traceback.format_exception(etype, value, tb)
- finally:
- etype = value = tb = None
- msg += '\n' + ''.join(lines)
- titles = self.groups[:]
- if self.test:
- titles.append(self.test.title)
- self.test.error(msg)
- else:
- self.writeline(VERBOSE_ERROR, msg)
- self.exceptions.append((titles, msg))
- if self.stopOnError:
- self.testDone()
- raise SystemExit(1)
- return
- def _displayList(self, title, color, list):
- for titles, message in list:
- header = ': '.join(titles)
- self._writetty('\n%s%s: %s%s\n' % (color, title, header,
- self.NORMAL))
- self._writetty(message)
- self._writetty('\n')
- if list:
- self._writetty('\n%s\n' % self.single_sep)
- return
- def report(self):
- self._writetty('\n')
- self._writetty(self.double_sep)
- self._writetty('\n')
- self._displayList('WARN', self.YELLOW, self.warnings)
- self._displayList('FAIL', self.RED, self.failures)
- self._displayList('EXCEPTION', self.WHITE, self.exceptions)
- self._writetty('\n')
- self._writetty(" Test Groups Run: %d\n" % self.totalGroups)
- self._writetty(" Test Items Run: %d\n" % self.totalTests)
- self._writetty(" Results Compared: %d\n" % self.totalComparisons)
- self._writetty("Total Time To Run: %0.3fs\n" % self.testTime)
- self._writetty('\n')
- # -- internal functions ------------------------------------------
- def _diff(self, expected, compared):
- # get the temporary file directory
- import tempfile
- tempdir = tempfile.gettempdir()
- if self._diffCommand:
- # create the expected output file
- expected_file = os.path.join(tempdir, 'expected')
- fd = open(expected_file, 'w')
- fd.write(str(expected))
- fd.close()
- # create the compared output file
- compared_file = os.path.join(tempdir, 'compared')
- fd = open(compared_file, 'w')
- fd.write(str(compared))
- fd.close()
- # run diff, capturing stdout and stderr
- cmdline = '%s -u %s %s' % (self._diffCommand, expected_file,
- compared_file)
- self.debug(cmdline)
- f_in, f_out = os.popen4(cmdline)
- f_in.close()
- self.debug(f_out.read())
- f_out.close()
- os.unlink(expected_file)
- os.unlink(compared_file)
- return