Skip to content

Commit

Permalink
Started unittest suite
Browse files Browse the repository at this point in the history
  • Loading branch information
ask committed Apr 15, 2013
1 parent c0880ff commit bcd89bc
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ devdatabase.db
bundle_version.gen
celeryd.log
celeryd.pid
nosetests.xml
coverage.xml
cover/
*.so
.tox/
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: python
python:
- 2.6
- 2.7
install:
- pip install --use-mirrors tox
script: TOXENV=py$(echo $TRAVIS_PYTHON_VERSION | tr -d .) tox
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ include Makefile
recursive-include Lib *.py
recursive-include Modules *.c *.h
recursive-include Doc *.rst *.py
recursive-include funtests *.py
recursive-include requirements *.txt
17 changes: 17 additions & 0 deletions billiard/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import atexit


def teardown():
cancelled = set()
import billiard.util
cancelled.add(billiard.util._exit_function)

try:
import multiprocessing.util
cancelled.add(multiprocessing.util._exit_function)
except (AttributeError, ImportError):
pass

atexit._exithandlers[:] = [
e for e in atexit._exithandlers if e[0] not in cancelled
]
85 changes: 85 additions & 0 deletions billiard/tests/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from __future__ import absolute_import

import sys


class WarningMessage(object):

"""Holds the result of a single showwarning() call."""

_WARNING_DETAILS = ('message', 'category', 'filename', 'lineno', 'file',
'line')

def __init__(self, message, category, filename, lineno, file=None,
line=None):
local_values = locals()
for attr in self._WARNING_DETAILS:
setattr(self, attr, local_values[attr])

self._category_name = category and category.__name__ or None

def __str__(self):
return ('{message : %r, category : %r, filename : %r, lineno : %s, '
'line : %r}' % (self.message, self._category_name,
self.filename, self.lineno, self.line))


class catch_warnings(object):

"""A context manager that copies and restores the warnings filter upon
exiting the context.
The 'record' argument specifies whether warnings should be captured by a
custom implementation of warnings.showwarning() and be appended to a list
returned by the context manager. Otherwise None is returned by the context
manager. The objects appended to the list are arguments whose attributes
mirror the arguments to showwarning().
The 'module' argument is to specify an alternative module to the module
named 'warnings' and imported under that name. This argument is only
useful when testing the warnings module itself.
"""

def __init__(self, record=False, module=None):
"""Specify whether to record warnings and if an alternative module
should be used other than sys.modules['warnings'].
For compatibility with Python 3.0, please consider all arguments to be
keyword-only.
"""
self._record = record
self._module = module is None and sys.modules['warnings'] or module
self._entered = False

def __repr__(self):
args = []
if self._record:
args.append('record=True')
if self._module is not sys.modules['warnings']:
args.append('module=%r' % self._module)
name = type(self).__name__
return '%s(%s)' % (name, ', '.join(args))

def __enter__(self):
if self._entered:
raise RuntimeError('Cannot enter %r twice' % self)
self._entered = True
self._filters = self._module.filters
self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning
if self._record:
log = []

def showwarning(*args, **kwargs):
log.append(WarningMessage(*args, **kwargs))

self._module.showwarning = showwarning
return log

def __exit__(self, *exc_info):
if not self._entered:
raise RuntimeError('Cannot exit %r without entering first' % self)
self._module.filters = self._filters
self._module.showwarning = self._showwarning
12 changes: 12 additions & 0 deletions billiard/tests/test_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from __future__ import absolute_import

import billiard

from .utils import Case


class test_billiard(Case):

def test_has_version(self):
self.assertTrue(billiard.__version__)
self.assertIsInstance(billiard.__version__, str)
144 changes: 144 additions & 0 deletions billiard/tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from __future__ import absolute_import
from __future__ import with_statement

import re
import sys
import warnings

try:
import unittest # noqa
unittest.skip
from unittest.util import safe_repr, unorderable_list_difference
except AttributeError:
import unittest2 as unittest # noqa
from unittest2.util import safe_repr, unorderable_list_difference # noqa

from .compat import catch_warnings

# -- adds assertWarns from recent unittest2, not in Python 2.7.


class _AssertRaisesBaseContext(object):

def __init__(self, expected, test_case, callable_obj=None,
expected_regex=None):
self.expected = expected
self.failureException = test_case.failureException
self.obj_name = None
if isinstance(expected_regex, basestring):
expected_regex = re.compile(expected_regex)
self.expected_regex = expected_regex


class _AssertWarnsContext(_AssertRaisesBaseContext):
"""A context manager used to implement TestCase.assertWarns* methods."""

def __enter__(self):
# The __warningregistry__'s need to be in a pristine state for tests
# to work properly.
warnings.resetwarnings()
for v in sys.modules.values():
if getattr(v, '__warningregistry__', None):
v.__warningregistry__ = {}
self.warnings_manager = catch_warnings(record=True)
self.warnings = self.warnings_manager.__enter__()
warnings.simplefilter('always', self.expected)
return self

def __exit__(self, exc_type, exc_value, tb):
self.warnings_manager.__exit__(exc_type, exc_value, tb)
if exc_type is not None:
# let unexpected exceptions pass through
return
try:
exc_name = self.expected.__name__
except AttributeError:
exc_name = str(self.expected)
first_matching = None
for m in self.warnings:
w = m.message
if not isinstance(w, self.expected):
continue
if first_matching is None:
first_matching = w
if (self.expected_regex is not None and
not self.expected_regex.search(str(w))):
continue
# store warning for later retrieval
self.warning = w
self.filename = m.filename
self.lineno = m.lineno
return
# Now we simply try to choose a helpful failure message
if first_matching is not None:
raise self.failureException(
'%r does not match %r' % (
self.expected_regex.pattern, str(first_matching)))
if self.obj_name:
raise self.failureException(
'%s not triggered by %s' % (exc_name, self.obj_name))
else:
raise self.failureException('%s not triggered' % exc_name)


class Case(unittest.TestCase):

def assertWarns(self, expected_warning):
return _AssertWarnsContext(expected_warning, self, None)

def assertWarnsRegex(self, expected_warning, expected_regex):
return _AssertWarnsContext(expected_warning, self,
None, expected_regex)

def assertDictContainsSubset(self, expected, actual, msg=None):
missing, mismatched = [], []

for key, value in expected.iteritems():
if key not in actual:
missing.append(key)
elif value != actual[key]:
mismatched.append('%s, expected: %s, actual: %s' % (
safe_repr(key), safe_repr(value),
safe_repr(actual[key])))

if not (missing or mismatched):
return

standard_msg = ''
if missing:
standard_msg = 'Missing: %s' % ','.join(map(safe_repr, missing))

if mismatched:
if standard_msg:
standard_msg += '; '
standard_msg += 'Mismatched values: %s' % (
','.join(mismatched))

self.fail(self._formatMessage(msg, standard_msg))

def assertItemsEqual(self, expected_seq, actual_seq, msg=None):
missing = unexpected = None
try:
expected = sorted(expected_seq)
actual = sorted(actual_seq)
except TypeError:
# Unsortable items (example: set(), complex(), ...)
expected = list(expected_seq)
actual = list(actual_seq)
missing, unexpected = unorderable_list_difference(
expected, actual)
else:
return self.assertSequenceEqual(expected, actual, msg=msg)

errors = []
if missing:
errors.append(
'Expected, but missing:\n %s' % (safe_repr(missing), ),
)
if unexpected:
errors.append(
'Unexpected, but present:\n %s' % (safe_repr(unexpected), ),
)
if errors:
standardMsg = '\n'.join(errors)
self.fail(self._formatMessage(msg, standardMsg))
5 changes: 5 additions & 0 deletions requirements/test-ci.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
coverage>=3.0
redis
pymongo
SQLAlchemy
PyOpenSSL
4 changes: 4 additions & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
unittest2>=0.4.0
nose
nose-cover3
mock
26 changes: 22 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,23 @@ def add_doc(m):
"""
long_description += open(os.path.join(HERE, 'CHANGES.txt')).read()

# -*- Installation Requires -*-

py_version = sys.version_info
is_jython = sys.platform.startswith('java')
is_pypy = hasattr(sys, 'pypy_version_info')


def strip_comments(l):
return l.split('#', 1)[0].strip()


def reqs(f):
return list(filter(None, [strip_comments(l) for l in open(
os.path.join(os.getcwd(), 'requirements', f)).readlines()]))

tests_require = reqs('test.txt')


def run_setup(with_extensions=True):
extensions = []
Expand Down Expand Up @@ -194,7 +211,7 @@ def run_setup(with_extensions=True):
url=meta['homepage'],
zip_safe=False,
license='BSD',
tests_require=['nose', 'nose-cover3'],
tests_require=tests_require,
test_suite='nose.collector',
classifiers=[
'Development Status :: 5 - Production/Stable',
Expand Down Expand Up @@ -223,6 +240,7 @@ def run_setup(with_extensions=True):
try:
run_setup(not (is_jython or is_pypy or is_py3k))
except BaseException:
import traceback
sys.stderr.write(BUILD_WARNING % '\n'.join(traceback.format_stack(), ))
run_setup(False)
if 'test' not in sys.argv:
import traceback
sys.stderr.write(BUILD_WARNING % '\n'.join(traceback.format_stack(), ))
run_setup(False)
37 changes: 37 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[tox]
envlist = py25,py26,py27

[testenv]
distribute = True
sitepackages = False
commands = nosetests

[testenv:py27]
basepython = python2.7
deps = -r{toxinidir}/requirements/test.txt
-r{toxinidir}/requirements/test-ci.txt
commands = nosetests --with-xunit \
--xunit-file={toxinidir}/nosetests.xml \
--with-coverage3 --cover3-xml \
--cover3-html-dir={toxinidir}/cover \
--cover3-xml-file={toxinidir}/coverage.xml

[testenv:py26]
basepython = python2.6
deps = -r{toxinidir}/requirements/test.txt
-r{toxinidir}/requirements/test-ci.txt
commands = nosetests --with-xunit \
--xunit-file={toxinidir}/nosetests.xml \
--with-coverage3 --cover3-xml \
--cover3-html-dir={toxinidir}/cover \
--cover3-xml-file={toxinidir}/coverage.xml

[testenv:py25]
basepython = python2.5
deps = -r{toxinidir}/requirements/test.txt
-r{toxinidir}/requirements/test-ci.txt
commands = nosetests --with-xunit \
--xunit-file={toxinidir}/nosetests.xml \
--with-coverage3 --cover3-xml \
--cover3-html-dir={toxinidir}/cover \
--cover3-xml-file={toxinidir}/coverage.xml

0 comments on commit bcd89bc

Please sign in to comment.