diff options
Diffstat (limited to 'blist/test/test_support.py')
-rw-r--r-- | blist/test/test_support.py | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/blist/test/test_support.py b/blist/test/test_support.py new file mode 100644 index 0000000..5d7550d --- /dev/null +++ b/blist/test/test_support.py @@ -0,0 +1,415 @@ +# This file taken from Python, licensed under the Python License Agreement + +from __future__ import print_function +"""Supporting definitions for the Python regression tests.""" + +if __name__ != 'blist.test.test_support': + raise ImportError('test_support must be imported from the test package') + +import sys + +class Error(Exception): + """Base class for regression test exceptions.""" + +class TestFailed(Error): + """Test failed.""" + +class TestSkipped(Error): + """Test skipped. + + This can be raised to indicate that a test was deliberatly + skipped, but not because a feature wasn't available. For + example, if some resource can't be used, such as the network + appears to be unavailable, this should be raised instead of + TestFailed. + """ + +class ResourceDenied(TestSkipped): + """Test skipped because it requested a disallowed resource. + + This is raised when a test calls requires() for a resource that + has not be enabled. It is used to distinguish between expected + and unexpected skips. + """ + +verbose = 1 # Flag set to 0 by regrtest.py +use_resources = None # Flag set to [] by regrtest.py +max_memuse = 0 # Disable bigmem tests (they will still be run with + # small sizes, to make sure they work.) + +# _original_stdout is meant to hold stdout at the time regrtest began. +# This may be "the real" stdout, or IDLE's emulation of stdout, or whatever. +# The point is to have some flavor of stdout the user can actually see. +_original_stdout = None +def record_original_stdout(stdout): + global _original_stdout + _original_stdout = stdout + +def get_original_stdout(): + return _original_stdout or sys.stdout + +def unload(name): + try: + del sys.modules[name] + except KeyError: + pass + +def unlink(filename): + import os + try: + os.unlink(filename) + except OSError: + pass + +def forget(modname): + '''"Forget" a module was ever imported by removing it from sys.modules and + deleting any .pyc and .pyo files.''' + unload(modname) + import os + for dirname in sys.path: + unlink(os.path.join(dirname, modname + os.extsep + 'pyc')) + # Deleting the .pyo file cannot be within the 'try' for the .pyc since + # the chance exists that there is no .pyc (and thus the 'try' statement + # is exited) but there is a .pyo file. + unlink(os.path.join(dirname, modname + os.extsep + 'pyo')) + +def is_resource_enabled(resource): + """Test whether a resource is enabled. Known resources are set by + regrtest.py.""" + return use_resources is not None and resource in use_resources + +def requires(resource, msg=None): + """Raise ResourceDenied if the specified resource is not available. + + If the caller's module is __main__ then automatically return True. The + possibility of False being returned occurs when regrtest.py is executing.""" + # see if the caller's module is __main__ - if so, treat as if + # the resource was set + if sys._getframe().f_back.f_globals.get("__name__") == "__main__": + return + if not is_resource_enabled(resource): + if msg is None: + msg = "Use of the `%s' resource not enabled" % resource + raise ResourceDenied(msg) + +FUZZ = 1e-6 + +def fcmp(x, y): # fuzzy comparison function + if type(x) == type(0.0) or type(y) == type(0.0): + try: + x, y = coerce(x, y) + fuzz = (abs(x) + abs(y)) * FUZZ + if abs(x-y) <= fuzz: + return 0 + except: + pass + elif type(x) == type(y) and type(x) in (type(()), type([])): + for i in range(min(len(x), len(y))): + outcome = fcmp(x[i], y[i]) + if outcome != 0: + return outcome + return cmp(len(x), len(y)) + return cmp(x, y) + +try: + str + have_unicode = 1 +except NameError: + have_unicode = 0 + +is_jython = sys.platform.startswith('java') + +import os +# Filename used for testing +if os.name == 'java': + # Jython disallows @ in module names + TESTFN = '$test' +elif os.name == 'riscos': + TESTFN = 'testfile' +else: + TESTFN = '@test' + # Unicode name only used if TEST_FN_ENCODING exists for the platform. + if have_unicode: + # Assuming sys.getfilesystemencoding()!=sys.getdefaultencoding() + # TESTFN_UNICODE is a filename that can be encoded using the + # file system encoding, but *not* with the default (ascii) encoding + if isinstance('', str): + # python -U + # XXX perhaps unicode() should accept Unicode strings? + TESTFN_UNICODE = "@test-\xe0\xf2" + else: + # 2 latin characters. + TESTFN_UNICODE = str("@test-\xe0\xf2", "latin-1") + TESTFN_ENCODING = sys.getfilesystemencoding() + +# Make sure we can write to TESTFN, try in /tmp if we can't +fp = None +try: + fp = open(TESTFN, 'w+') +except IOError: + TMP_TESTFN = os.path.join('/tmp', TESTFN) + try: + fp = open(TMP_TESTFN, 'w+') + TESTFN = TMP_TESTFN + del TMP_TESTFN + except IOError: + print(('WARNING: tests will fail, unable to write to: %s or %s' % + (TESTFN, TMP_TESTFN))) +if fp is not None: + fp.close() + unlink(TESTFN) +del os, fp + +def findfile(file, here=__file__): + """Try to find a file on sys.path and the working directory. If it is not + found the argument passed to the function is returned (this does not + necessarily signal failure; could still be the legitimate path).""" + import os + if os.path.isabs(file): + return file + path = sys.path + path = [os.path.dirname(here)] + path + for dn in path: + fn = os.path.join(dn, file) + if os.path.exists(fn): return fn + return file + +def verify(condition, reason='test failed'): + """Verify that condition is true. If not, raise TestFailed. + + The optional argument reason can be given to provide + a better error text. + """ + + if not condition: + raise TestFailed(reason) + +def vereq(a, b): + """Raise TestFailed if a == b is false. + + This is better than verify(a == b) because, in case of failure, the + error message incorporates repr(a) and repr(b) so you can see the + inputs. + + Note that "not (a == b)" isn't necessarily the same as "a != b"; the + former is tested. + """ + + if not (a == b): + raise TestFailed("%r == %r" % (a, b)) + +def sortdict(dict): + "Like repr(dict), but in sorted order." + items = list(dict.items()) + items.sort() + reprpairs = ["%r: %r" % pair for pair in items] + withcommas = ", ".join(reprpairs) + return "{%s}" % withcommas + +def check_syntax(statement): + try: + compile(statement, '<string>', 'exec') + except SyntaxError: + pass + else: + print('Missing SyntaxError: "%s"' % statement) + +def open_urlresource(url): + import urllib.request, urllib.parse, urllib.error, urllib.parse + import os.path + + filename = urllib.parse.urlparse(url)[2].split('/')[-1] # '/': it's URL! + + for path in [os.path.curdir, os.path.pardir]: + fn = os.path.join(path, filename) + if os.path.exists(fn): + return open(fn) + + requires('urlfetch') + print('\tfetching %s ...' % url, file=get_original_stdout()) + fn, _ = urllib.request.urlretrieve(url, filename) + return open(fn) + +#======================================================================= +# Decorator for running a function in a different locale, correctly resetting +# it afterwards. + +def run_with_locale(catstr, *locales): + def decorator(func): + def inner(*args, **kwds): + try: + import locale + category = getattr(locale, catstr) + orig_locale = locale.setlocale(category) + except AttributeError: + # if the test author gives us an invalid category string + raise + except: + # cannot retrieve original locale, so do nothing + locale = orig_locale = None + else: + for loc in locales: + try: + locale.setlocale(category, loc) + break + except: + pass + + # now run the function, resetting the locale on exceptions + try: + return func(*args, **kwds) + finally: + if locale and orig_locale: + locale.setlocale(category, orig_locale) + inner.__name__ = func.__name__ + inner.__doc__ = func.__doc__ + return inner + return decorator + +#======================================================================= +# Big-memory-test support. Separate from 'resources' because memory use should be configurable. + +# Some handy shorthands. Note that these are used for byte-limits as well +# as size-limits, in the various bigmem tests +_1M = 1024*1024 +_1G = 1024 * _1M +_2G = 2 * _1G + +def set_memlimit(limit): + import re + global max_memuse + sizes = { + 'k': 1024, + 'm': _1M, + 'g': _1G, + 't': 1024*_1G, + } + m = re.match(r'(\d+(\.\d+)?) (K|M|G|T)b?$', limit, + re.IGNORECASE | re.VERBOSE) + if m is None: + raise ValueError('Invalid memory limit %r' % (limit,)) + memlimit = int(float(m.group(1)) * sizes[m.group(3).lower()]) + if memlimit < 2.5*_1G: + raise ValueError('Memory limit %r too low to be useful' % (limit,)) + max_memuse = memlimit + +def bigmemtest(minsize, memuse, overhead=5*_1M): + """Decorator for bigmem tests. + + 'minsize' is the minimum useful size for the test (in arbitrary, + test-interpreted units.) 'memuse' is the number of 'bytes per size' for + the test, or a good estimate of it. 'overhead' specifies fixed overhead, + independant of the testsize, and defaults to 5Mb. + + The decorator tries to guess a good value for 'size' and passes it to + the decorated test function. If minsize * memuse is more than the + allowed memory use (as defined by max_memuse), the test is skipped. + Otherwise, minsize is adjusted upward to use up to max_memuse. + """ + def decorator(f): + def wrapper(self): + if not max_memuse: + # If max_memuse is 0 (the default), + # we still want to run the tests with size set to a few kb, + # to make sure they work. We still want to avoid using + # too much memory, though, but we do that noisily. + maxsize = 5147 + self.failIf(maxsize * memuse + overhead > 20 * _1M) + else: + maxsize = int((max_memuse - overhead) / memuse) + if maxsize < minsize: + # Really ought to print 'test skipped' or something + if verbose: + sys.stderr.write("Skipping %s because of memory " + "constraint\n" % (f.__name__,)) + return + # Try to keep some breathing room in memory use + maxsize = max(maxsize - 50 * _1M, minsize) + return f(self, maxsize) + wrapper.minsize = minsize + wrapper.memuse = memuse + wrapper.overhead = overhead + return wrapper + return decorator + +#======================================================================= +# Preliminary PyUNIT integration. + +from . import unittest + + +class BasicTestRunner: + def run(self, test): + result = unittest.TestResult() + test(result) + return result + +def run_suite(suite, testclass=None): + """Run tests from a unittest.TestSuite-derived class.""" + if verbose: + runner = unittest.TextTestRunner(sys.stdout, verbosity=2) + else: + runner = BasicTestRunner() + + result = runner.run(suite) + if not result.wasSuccessful(): + if len(result.errors) == 1 and not result.failures: + err = result.errors[0][1] + elif len(result.failures) == 1 and not result.errors: + err = result.failures[0][1] + else: + if testclass is None: + msg = "errors occurred; run in verbose mode for details" + else: + msg = "errors occurred in %s.%s" \ + % (testclass.__module__, testclass.__name__) + raise TestFailed(msg) + raise TestFailed(err) + + +def run_unittest(*classes): + """Run tests from unittest.TestCase-derived classes.""" + suite = unittest.TestSuite() + for cls in classes: + if isinstance(cls, (unittest.TestSuite, unittest.TestCase)): + suite.addTest(cls) + else: + suite.addTest(unittest.makeSuite(cls)) + if len(classes)==1: + testclass = classes[0] + else: + testclass = None + run_suite(suite, testclass) + + +#======================================================================= +# doctest driver. + +def run_doctest(module, verbosity=None): + """Run doctest on the given module. Return (#failures, #tests). + + If optional argument verbosity is not specified (or is None), pass + test_support's belief about verbosity on to doctest. Else doctest's + usual behavior is used (it searches sys.argv for -v). + """ + + import doctest + + if verbosity is None: + verbosity = verbose + else: + verbosity = None + + # Direct doctest output (normally just errors) to real stdout; doctest + # output shouldn't be compared by regrtest. + save_stdout = sys.stdout + sys.stdout = get_original_stdout() + try: + f, t = doctest.testmod(module, verbose=verbosity) + if f: + raise TestFailed("%d of %d doctests failed" % (f, t)) + finally: + sys.stdout = save_stdout + if verbose: + print('doctest (%s) ... %d tests with zero failures' % (module.__name__, t)) + return f, t |