216 lines
5.9 KiB
Python
216 lines
5.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
chibitest
|
|
~~~~~~~~~
|
|
|
|
chibitest is a simple unit testing module. Less code is less bugs.
|
|
Inspired by Oktest, http://www.kuwata-lab.com/oktest/.
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import sys
|
|
import inspect
|
|
import traceback
|
|
from difflib import unified_diff
|
|
from collections import namedtuple
|
|
|
|
|
|
Result = namedtuple('Result', ('func', 'name', 'failure'))
|
|
|
|
|
|
def _exc_name(exception_class):
|
|
if not inspect.isclass(exception_class):
|
|
exception_class = exception_class.__class__
|
|
|
|
return '<{}.{}>'.format(
|
|
exception_class.__module__,
|
|
exception_class.__name__)
|
|
|
|
|
|
class ExtendedDict(dict):
|
|
def __getattr__(self, name):
|
|
if name in self:
|
|
return self[name]
|
|
else:
|
|
return None
|
|
|
|
def __setattr__(self, name, value):
|
|
self[name] = value
|
|
|
|
|
|
class AssertionObject(object):
|
|
def __init__(self, target):
|
|
self._target = target
|
|
|
|
def __lt__(self, other):
|
|
if not self._target < other:
|
|
raise AssertionError('{!r} < {!r}'.format(self._target, other))
|
|
|
|
def __le__(self, other):
|
|
if not self._target <= other:
|
|
raise AssertionError('{!r} <= {!r}'.format(self._target, other))
|
|
|
|
def __eq__(self, other):
|
|
if not self._target == other:
|
|
raise AssertionError('{!r} == {!r}'.format(self._target, other))
|
|
|
|
def __ne__(self, other):
|
|
if not self._target != other:
|
|
raise AssertionError('{!r} != {!r}'.format(self._target, other))
|
|
|
|
def __gt__(self, other):
|
|
if not self._target > other:
|
|
raise AssertionError('{!r} > {!r}'.format(self._target, other))
|
|
|
|
def __ge__(self, other):
|
|
if not self._target >= other:
|
|
raise AssertionError('{!r} >= {!r}'.format(self._target, other))
|
|
|
|
def length(self, other):
|
|
target_length = len(self._target)
|
|
|
|
if target_length > other:
|
|
raise AssertionError('Higher than desired length: {!r} > {!r}'
|
|
.format(target_length, other))
|
|
elif target_length < other:
|
|
raise AssertionError('Lower than desired length: {!r} < {!r}'
|
|
.format(target_length, other))
|
|
|
|
def diff(self, other):
|
|
if self._target != other:
|
|
difference = unified_diff(
|
|
other.splitlines(True),
|
|
self._target.splitlines(True))
|
|
raise AssertionError(''.join(difference))
|
|
|
|
def contains(self, other):
|
|
if other not in self._target:
|
|
raise AssertionError('{!r} in {!r}'.format(other, self._target))
|
|
|
|
def not_contains(self, other):
|
|
if other in self._target:
|
|
raise AssertionError('{!r} not in {!r}'.format(other, self._target))
|
|
|
|
def raises(self, exception_class=Exception):
|
|
name = _exc_name(exception_class)
|
|
|
|
# ``exception_class`` raised. Good!
|
|
# Anything other than ``exception_class`` raised. Wrong!
|
|
# No exception. Wrong!
|
|
try:
|
|
self._target()
|
|
except exception_class:
|
|
pass
|
|
except Exception as e:
|
|
raise AssertionError('Expected {}, but got {}:\n{}'
|
|
.format(name, _exc_name(e), e))
|
|
else:
|
|
raise AssertionError('{} not raised'.format(name))
|
|
|
|
|
|
def not_raises(self, exception_class=Exception):
|
|
name = _exc_name(exception_class)
|
|
|
|
# No exception raised. Good!
|
|
# ``exception_class`` raised. Wrong!
|
|
# Any exception raised. Wrong!
|
|
try:
|
|
self._target()
|
|
except exception_class as e:
|
|
raise AssertionError('{} raised:\n{}'.format(name, e))
|
|
except Exception as e:
|
|
raise AssertionError('Expected {} when failing, but got {}:\n{}'
|
|
.format(name, _exc_name(e), e))
|
|
|
|
|
|
# A nicer alias.
|
|
ok = AssertionObject
|
|
|
|
|
|
class TestCase(object):
|
|
def __init__(self, config):
|
|
self.config = config
|
|
self._tests = []
|
|
|
|
for t in dir(self):
|
|
if t.startswith('test_'):
|
|
self._tests.append(self._wrap_test(getattr(self, t)))
|
|
|
|
def add_test(self, func):
|
|
self._tests.append(self._wrap_test(func))
|
|
|
|
def _wrap_test(self, func):
|
|
def catch_exception():
|
|
failure = None
|
|
|
|
try:
|
|
func()
|
|
except AssertionError as e:
|
|
failure = str(e)
|
|
except Exception as e:
|
|
failure = ''.join(traceback.format_exception(
|
|
*sys.exc_info())).strip()
|
|
|
|
doc = ''
|
|
if func.__doc__:
|
|
doc = func.__doc__.lstrip()
|
|
idx = doc.find('\n')
|
|
if idx > 0:
|
|
doc = doc[:idx]
|
|
|
|
return Result(func.__name__, doc or None, failure)
|
|
|
|
return catch_exception
|
|
|
|
def setup(self):
|
|
pass
|
|
|
|
def teardown(self):
|
|
pass
|
|
|
|
def run(self):
|
|
self.setup()
|
|
for test in self._tests:
|
|
yield test()
|
|
self.teardown()
|
|
|
|
|
|
def runner(testcases, setup_func=None, teardown_func=None, config={}):
|
|
line = '*' * 80
|
|
passed = failed = 0
|
|
config = ExtendedDict(config)
|
|
|
|
if setup_func:
|
|
setup_func()
|
|
|
|
for testcase in testcases:
|
|
tests = testcase(config)
|
|
|
|
print('>> {}'.format(tests.name if hasattr(tests, 'name')
|
|
else testcase.__name__))
|
|
|
|
for result in tests.run():
|
|
name = result.name or result.func
|
|
if result.failure is not None:
|
|
failed += 1
|
|
|
|
if result.failure:
|
|
print('{} ... FAILED\n{}\n{}\n{}'
|
|
.format(name, line, result.failure, line))
|
|
else:
|
|
print('{} ... FAILED'.format(name))
|
|
else:
|
|
passed += 1
|
|
print('{} ... PASSED'.format(name))
|
|
|
|
print()
|
|
|
|
if teardown_func:
|
|
teardown_func()
|
|
|
|
print('{} passed; {} failed.'.format(passed, failed))
|
|
if failed > 0:
|
|
sys.exit(1)
|