diff --git a/Tests/tester.py b/Tests/tester.py new file mode 100644 index 000000000..4476aced1 --- /dev/null +++ b/Tests/tester.py @@ -0,0 +1,388 @@ +from __future__ import print_function + +# require that deprecation warnings are triggered +import warnings +warnings.simplefilter('default') +# temporarily turn off resource warnings that warn about unclosed +# files in the test scripts. +try: + warnings.filterwarnings("ignore", category=ResourceWarning) +except NameError: + # we expect a NameError on py2.x, since it doesn't have ResourceWarnings. + pass + +import sys +py3 = (sys.version_info >= (3, 0)) + +# some test helpers + +_target = None +_tempfiles = [] +_logfile = None + + +def success(): + import sys + success.count += 1 + if _logfile: + print(sys.argv[0], success.count, failure.count, file=_logfile) + return True + + +def failure(msg=None, frame=None): + import sys + import linecache + failure.count += 1 + if _target: + if frame is None: + frame = sys._getframe() + while frame.f_globals.get("__name__") != _target.__name__: + frame = frame.f_back + location = (frame.f_code.co_filename, frame.f_lineno) + prefix = "%s:%d: " % location + line = linecache.getline(*location) + print(prefix + line.strip() + " failed:") + if msg: + print("- " + msg) + if _logfile: + print(sys.argv[0], success.count, failure.count, file=_logfile) + return False + +success.count = failure.count = 0 + + +# predicates + +def assert_true(v, msg=None): + if v: + success() + else: + failure(msg or "got %r, expected true value" % v) + + +def assert_false(v, msg=None): + if v: + failure(msg or "got %r, expected false value" % v) + else: + success() + + +def assert_equal(a, b, msg=None): + if a == b: + success() + else: + failure(msg or "got %r, expected %r" % (a, b)) + + +def assert_almost_equal(a, b, msg=None, eps=1e-6): + if abs(a-b) < eps: + success() + else: + failure(msg or "got %r, expected %r" % (a, b)) + + +def assert_deep_equal(a, b, msg=None): + try: + if len(a) == len(b): + if all([x == y for x, y in zip(a, b)]): + success() + else: + failure(msg or "got %s, expected %s" % (a, b)) + else: + failure(msg or "got length %s, expected %s" % (len(a), len(b))) + except: + assert_equal(a, b, msg) + + +def assert_greater(a, b, msg=None): + if a > b: + success() + else: + failure(msg or "%r unexpectedly not greater than %r" % (a, b)) + + +def assert_greater_equal(a, b, msg=None): + if a >= b: + success() + else: + failure( + msg or "%r unexpectedly not greater than or equal to %r" % (a, b)) + + +def assert_less(a, b, msg=None): + if a < b: + success() + else: + failure(msg or "%r unexpectedly not less than %r" % (a, b)) + + +def assert_less_equal(a, b, msg=None): + if a <= b: + success() + else: + failure( + msg or "%r unexpectedly not less than or equal to %r" % (a, b)) + + +def assert_is_instance(a, b, msg=None): + if isinstance(a, b): + success() + else: + failure(msg or "got %r, expected %r" % (type(a), b)) + + +def assert_in(a, b, msg=None): + if a in b: + success() + else: + failure(msg or "%r unexpectedly not in %r" % (a, b)) + + +def assert_match(v, pattern, msg=None): + import re + if re.match(pattern, v): + success() + else: + failure(msg or "got %r, doesn't match pattern %r" % (v, pattern)) + + +def assert_exception(exc_class, func): + import sys + import traceback + try: + func() + except exc_class: + success() + except: + failure("expected %r exception, got %r" % ( + exc_class.__name__, sys.exc_info()[0].__name__)) + traceback.print_exc() + else: + failure("expected %r exception, got no exception" % exc_class.__name__) + + +def assert_no_exception(func): + import sys + import traceback + try: + func() + except: + failure("expected no exception, got %r" % sys.exc_info()[0].__name__) + traceback.print_exc() + else: + success() + + +def assert_warning(warn_class, func): + # note: this assert calls func three times! + import warnings + + def warn_error(message, category=UserWarning, **options): + raise category(message) + + def warn_ignore(message, category=UserWarning, **options): + pass + warn = warnings.warn + result = None + try: + warnings.warn = warn_ignore + assert_no_exception(func) + result = func() + warnings.warn = warn_error + assert_exception(warn_class, func) + finally: + warnings.warn = warn # restore + return result + +# helpers + +from io import BytesIO + + +def fromstring(data): + from PIL import Image + return Image.open(BytesIO(data)) + + +def tostring(im, format, **options): + out = BytesIO() + im.save(out, format, **options) + return out.getvalue() + + +def lena(mode="RGB", cache={}): + from PIL import Image + im = cache.get(mode) + if im is None: + if mode == "RGB": + im = Image.open("Images/lena.ppm") + elif mode == "F": + im = lena("L").convert(mode) + elif mode[:4] == "I;16": + im = lena("I").convert(mode) + else: + im = lena("RGB").convert(mode) + cache[mode] = im + return im + + +def assert_image(im, mode, size, msg=None): + if mode is not None and im.mode != mode: + failure(msg or "got mode %r, expected %r" % (im.mode, mode)) + elif size is not None and im.size != size: + failure(msg or "got size %r, expected %r" % (im.size, size)) + else: + success() + + +def assert_image_equal(a, b, msg=None): + if a.mode != b.mode: + failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) + elif a.size != b.size: + failure(msg or "got size %r, expected %r" % (a.size, b.size)) + elif a.tobytes() != b.tobytes(): + failure(msg or "got different content") + else: + success() + + +def assert_image_completely_equal(a, b, msg=None): + if a != b: + failure(msg or "images different") + else: + success() + + +def assert_image_similar(a, b, epsilon, msg=None): + epsilon = float(epsilon) + if a.mode != b.mode: + return failure(msg or "got mode %r, expected %r" % (a.mode, b.mode)) + elif a.size != b.size: + return failure(msg or "got size %r, expected %r" % (a.size, b.size)) + diff = 0 + try: + ord(b'0') + for abyte, bbyte in zip(a.tobytes(), b.tobytes()): + diff += abs(ord(abyte)-ord(bbyte)) + except: + for abyte, bbyte in zip(a.tobytes(), b.tobytes()): + diff += abs(abyte-bbyte) + ave_diff = float(diff)/(a.size[0]*a.size[1]) + if epsilon < ave_diff: + return failure( + msg or "average pixel value difference %.4f > epsilon %.4f" % ( + ave_diff, epsilon)) + else: + return success() + + +def tempfile(template, *extra): + import os + import os.path + import sys + import tempfile + files = [] + root = os.path.join(tempfile.gettempdir(), 'pillow-tests') + try: + os.mkdir(root) + except OSError: + pass + for temp in (template,) + extra: + assert temp[:5] in ("temp.", "temp_") + name = os.path.basename(sys.argv[0]) + name = temp[:4] + os.path.splitext(name)[0][4:] + name = name + "_%d" % len(_tempfiles) + temp[4:] + name = os.path.join(root, name) + files.append(name) + _tempfiles.extend(files) + return files[0] + + +# test runner + +def run(): + global _target, _tests, run + import sys + import traceback + _target = sys.modules["__main__"] + run = None # no need to run twice + tests = [] + for name, value in list(vars(_target).items()): + if name[:5] == "test_" and type(value) is type(success): + tests.append((value.__code__.co_firstlineno, name, value)) + tests.sort() # sort by line + for lineno, name, func in tests: + try: + _tests = [] + func() + for func, args in _tests: + func(*args) + except: + t, v, tb = sys.exc_info() + tb = tb.tb_next + if tb: + failure(frame=tb.tb_frame) + traceback.print_exception(t, v, tb) + else: + print("%s:%d: cannot call test function: %s" % ( + sys.argv[0], lineno, v)) + failure.count += 1 + + +def yield_test(function, *args): + # collect delayed/generated tests + _tests.append((function, args)) + + +def skip(msg=None): + import os + print("skip") + os._exit(0) # don't run exit handlers + + +def ignore(pattern): + """Tells the driver to ignore messages matching the pattern, for the + duration of the current test.""" + print('ignore: %s' % pattern) + + +def _setup(): + global _logfile + + import sys + if "--coverage" in sys.argv: + # Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + import coverage + cov = coverage.coverage(auto_data=True, include="PIL/*") + cov.start() + + def report(): + if run: + run() + if success.count and not failure.count: + print("ok") + # only clean out tempfiles if test passed + import os + import os.path + import tempfile + for file in _tempfiles: + try: + os.remove(file) + except OSError: + pass # report? + temp_root = os.path.join(tempfile.gettempdir(), 'pillow-tests') + try: + os.rmdir(temp_root) + except OSError: + pass + + import atexit + atexit.register(report) + + if "--log" in sys.argv: + _logfile = open("test.log", "a") + + +_setup()