changeset 5019:cf8c3cf122a0

tests: Make `parameterized` stuff work with both pytest and nose - Use `nose.tools.nottest` if `nose` is available - Use `pytest.mark.skipIf` if `pytest` is available - Rename from `nose_parametrized` to `parameterized` since it's no longer nose-specific
author Marc Abramowitz <marc@marc-abramowitz.com>
date Wed, 15 Apr 2015 14:58:12 -0700
parents 9c5e6984bd0e
children b658d5b3d149
files kallithea/tests/__init__.py kallithea/tests/nose_parametrized.py kallithea/tests/parameterized.py
diffstat 3 files changed, 242 insertions(+), 225 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/tests/__init__.py	Tue Apr 14 17:34:44 2015 -0700
+++ b/kallithea/tests/__init__.py	Wed Apr 15 14:58:12 2015 -0700
@@ -55,7 +55,7 @@
 from kallithea.lib.compat import unittest
 from kallithea import is_windows
 from kallithea.model.db import User
-from kallithea.tests.nose_parametrized import parameterized
+from kallithea.tests.parameterized import parameterized
 from kallithea.lib.utils2 import safe_str
 
 
--- a/kallithea/tests/nose_parametrized.py	Tue Apr 14 17:34:44 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,224 +0,0 @@
-import re
-import new
-import inspect
-import logging
-import logging.handlers
-from functools import wraps
-
-from nose.tools import nottest
-from unittest import TestCase
-
-
-def _terrible_magic_get_defining_classes():
-    """ Returns the set of parent classes of the class currently being defined.
-        Will likely only work if called from the ``parameterized`` decorator.
-        This function is entirely @brandon_rhodes's fault, as he suggested
-        the implementation: http://stackoverflow.com/a/8793684/71522
-        """
-    stack = inspect.stack()
-    if len(stack) <= 4:
-        return []
-    frame = stack[3]
-    code_context = frame[4][0].strip()
-    if not code_context.startswith("class "):
-        return []
-    _, parents = code_context.split("(", 1)
-    parents, _ = parents.rsplit(")", 1)
-    return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
-
-
-def parameterized(input):
-    """ Parameterize a test case:
-        >>> add1_tests = [(1, 2), (2, 3)]
-        >>> class TestFoo(object):
-        ...     @parameterized(add1_tests)
-        ...     def test_add1(self, input, expected):
-        ...         assert_equal(add1(input), expected)
-        >>> @parameterized(add1_tests)
-        ... def test_add1(input, expected):
-        ...     assert_equal(add1(input), expected)
-        >>>
-        """
-
-    if not hasattr(input, "__iter__"):
-        raise ValueError("expected iterable input; got %r" % (input,))
-
-    def parameterized_helper(f):
-        attached_instance_method = [False]
-
-        parent_classes = _terrible_magic_get_defining_classes()
-        if any(issubclass(cls, TestCase) for cls in parent_classes):
-            raise Exception("Warning: '@parameterized' tests won't work "
-                            "inside subclasses of 'TestCase' - use "
-                            "'@parameterized.expand' instead")
-
-        @wraps(f)
-        def parameterized_helper_method(self=None):
-            if self is not None and not attached_instance_method[0]:
-                # confusingly, we need to create a named instance method and
-                # attach that to the class...
-                cls = self.__class__
-                im_f = new.instancemethod(f, None, cls)
-                setattr(cls, f.__name__, im_f)
-                attached_instance_method[0] = True
-            for args in input:
-                if isinstance(args, basestring):
-                    args = [args]
-                # ... then pull that named instance method off, turning it into
-                # a bound method ...
-                if self is not None:
-                    args = [getattr(self, f.__name__)] + list(args)
-                else:
-                    args = [f] + list(args)
-                # ... then yield that as a tuple. If those steps aren't
-                # followed precicely, Nose gets upset and doesn't run the test
-                # or doesn't run setup methods.
-                yield tuple(args)
-
-        f.__name__ = "_helper_for_%s" % (f.__name__,)
-        parameterized_helper_method.parameterized_input = input
-        parameterized_helper_method.parameterized_func = f
-        return parameterized_helper_method
-
-    return parameterized_helper
-
-
-def to_safe_name(s):
-    return re.sub("[^a-zA-Z0-9_]", "", s)
-
-
-def parameterized_expand_helper(func_name, func, args):
-    def parameterized_expand_helper_helper(self=()):
-        if self != ():
-            self = (self,)
-        return func(*(self + args))
-    parameterized_expand_helper_helper.__name__ = str(func_name)
-    return parameterized_expand_helper_helper
-
-
-def parameterized_expand(input):
-    """ A "brute force" method of parameterizing test cases. Creates new test
-        cases and injects them into the namespace that the wrapped function
-        is being defined in. Useful for parameterizing tests in subclasses
-        of 'UnitTest', where Nose test generators don't work.
-
-        >>> @parameterized.expand([("foo", 1, 2)])
-        ... def test_add1(name, input, expected):
-        ...     actual = add1(input)
-        ...     assert_equal(actual, expected)
-        ...
-        >>> locals()
-        ... 'test_add1_foo_0': <function ...> ...
-        >>>
-        """
-
-    def parameterized_expand_wrapper(f):
-        stack = inspect.stack()
-        frame = stack[1]
-        frame_locals = frame[0].f_locals
-
-        base_name = f.__name__
-        for num, args in enumerate(input):
-            name_suffix = "_%s" % (num,)
-            if len(args) > 0 and isinstance(args[0], basestring):
-                name_suffix += "_" + to_safe_name(args[0])
-            name = base_name + name_suffix
-            new_func = parameterized_expand_helper(name, f, args)
-            frame_locals[name] = new_func
-        return nottest(f)
-    return parameterized_expand_wrapper
-
-parameterized.expand = parameterized_expand
-
-
-def assert_contains(haystack, needle):
-    if needle not in haystack:
-        raise AssertionError("%r not in %r" % (needle, haystack))
-
-
-def assert_not_contains(haystack, needle):
-    if needle in haystack:
-        raise AssertionError("%r in %r" % (needle, haystack))
-
-
-def assert_raises(func, exc_type, str_contains=None, repr_contains=None):
-    try:
-        func()
-    except exc_type, e:
-        if str_contains is not None and str_contains not in str(e):
-            raise AssertionError("%s raised, but %r does not contain %r"
-                                 % (exc_type, str(e), str_contains))
-        if repr_contains is not None and repr_contains not in repr(e):
-            raise AssertionError("%s raised, but %r does not contain %r"
-                                 % (exc_type, repr(e), repr_contains))
-        return e
-    else:
-        raise AssertionError("%s not raised" % (exc_type,))
-
-
-log_handler = None
-
-
-def setup_logging():
-    """ Configures a log handler which will capure log messages during a test.
-        The ``logged_messages`` and ``assert_no_errors_logged`` functions can be
-        used to make assertions about these logged messages.
-
-        For example::
-
-            from ensi_common.testing import (
-                setup_logging, teardown_logging, assert_no_errors_logged,
-                assert_logged,
-            )
-
-            class TestWidget(object):
-                def setup(self):
-                    setup_logging()
-
-                def teardown(self):
-                    assert_no_errors_logged()
-                    teardown_logging()
-
-                def test_that_will_fail(self):
-                    log.warning("this warning message will trigger a failure")
-
-                def test_that_will_pass(self):
-                    log.info("but info messages are ok")
-                    assert_logged("info messages are ok")
-        """
-
-    global log_handler
-    if log_handler is not None:
-        logging.getLogger().removeHandler(log_handler)
-    log_handler = logging.handlers.BufferingHandler(1000)
-    formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
-    log_handler.setFormatter(formatter)
-    logging.getLogger().addHandler(log_handler)
-
-
-def teardown_logging():
-    global log_handler
-    if log_handler is not None:
-        logging.getLogger().removeHandler(log_handler)
-        log_handler = None
-
-
-def logged_messages():
-    assert log_handler, "setup_logging not called"
-    return [(log_handler.format(record), record) for record in log_handler.buffer]
-
-
-def assert_no_errors_logged():
-    for _, record in logged_messages():
-        if record.levelno >= logging.WARNING:
-            # Assume that the nose log capture plugin is being used, so it will
-            # show the exception.
-            raise AssertionError("an unexpected error was logged")
-
-
-def assert_logged(expected_msg_contents):
-    for msg, _ in logged_messages():
-        if expected_msg_contents in msg:
-            return
-    raise AssertionError("no logged message contains %r"
-                         % (expected_msg_contents,))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/tests/parameterized.py	Wed Apr 15 14:58:12 2015 -0700
@@ -0,0 +1,241 @@
+import re
+import new
+import inspect
+import logging
+import logging.handlers
+from functools import wraps
+
+from unittest import TestCase
+
+
+def skip_test(func):
+    try:
+        from nose.tools import nottest
+    except ImportError:
+        pass
+    else:
+        func = nottest(func)
+
+    try:
+        import pytest
+    except ImportError:
+        pass
+    else:
+        func = pytest.mark.skipIf(True, func)
+
+    return func
+
+
+def _terrible_magic_get_defining_classes():
+    """ Returns the set of parent classes of the class currently being defined.
+        Will likely only work if called from the ``parameterized`` decorator.
+        This function is entirely @brandon_rhodes's fault, as he suggested
+        the implementation: http://stackoverflow.com/a/8793684/71522
+        """
+    stack = inspect.stack()
+    if len(stack) <= 4:
+        return []
+    frame = stack[3]
+    code_context = frame[4][0].strip()
+    if not code_context.startswith("class "):
+        return []
+    _, parents = code_context.split("(", 1)
+    parents, _ = parents.rsplit(")", 1)
+    return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
+
+
+def parameterized(input):
+    """ Parameterize a test case:
+        >>> add1_tests = [(1, 2), (2, 3)]
+        >>> class TestFoo(object):
+        ...     @parameterized(add1_tests)
+        ...     def test_add1(self, input, expected):
+        ...         assert_equal(add1(input), expected)
+        >>> @parameterized(add1_tests)
+        ... def test_add1(input, expected):
+        ...     assert_equal(add1(input), expected)
+        >>>
+        """
+
+    if not hasattr(input, "__iter__"):
+        raise ValueError("expected iterable input; got %r" % (input,))
+
+    def parameterized_helper(f):
+        attached_instance_method = [False]
+
+        parent_classes = _terrible_magic_get_defining_classes()
+        if any(issubclass(cls, TestCase) for cls in parent_classes):
+            raise Exception("Warning: '@parameterized' tests won't work "
+                            "inside subclasses of 'TestCase' - use "
+                            "'@parameterized.expand' instead")
+
+        @wraps(f)
+        def parameterized_helper_method(self=None):
+            if self is not None and not attached_instance_method[0]:
+                # confusingly, we need to create a named instance method and
+                # attach that to the class...
+                cls = self.__class__
+                im_f = new.instancemethod(f, None, cls)
+                setattr(cls, f.__name__, im_f)
+                attached_instance_method[0] = True
+            for args in input:
+                if isinstance(args, basestring):
+                    args = [args]
+                # ... then pull that named instance method off, turning it into
+                # a bound method ...
+                if self is not None:
+                    args = [getattr(self, f.__name__)] + list(args)
+                else:
+                    args = [f] + list(args)
+                # ... then yield that as a tuple. If those steps aren't
+                # followed precicely, Nose gets upset and doesn't run the test
+                # or doesn't run setup methods.
+                yield tuple(args)
+
+        f.__name__ = "_helper_for_%s" % (f.__name__,)
+        parameterized_helper_method.parameterized_input = input
+        parameterized_helper_method.parameterized_func = f
+        return parameterized_helper_method
+
+    return parameterized_helper
+
+
+def to_safe_name(s):
+    return re.sub("[^a-zA-Z0-9_]", "", s)
+
+
+def parameterized_expand_helper(func_name, func, args):
+    def parameterized_expand_helper_helper(self=()):
+        if self != ():
+            self = (self,)
+        return func(*(self + args))
+    parameterized_expand_helper_helper.__name__ = str(func_name)
+    return parameterized_expand_helper_helper
+
+
+def parameterized_expand(input):
+    """ A "brute force" method of parameterizing test cases. Creates new test
+        cases and injects them into the namespace that the wrapped function
+        is being defined in. Useful for parameterizing tests in subclasses
+        of 'UnitTest', where Nose test generators don't work.
+
+        >>> @parameterized.expand([("foo", 1, 2)])
+        ... def test_add1(name, input, expected):
+        ...     actual = add1(input)
+        ...     assert_equal(actual, expected)
+        ...
+        >>> locals()
+        ... 'test_add1_foo_0': <function ...> ...
+        >>>
+        """
+
+    def parameterized_expand_wrapper(f):
+        stack = inspect.stack()
+        frame = stack[1]
+        frame_locals = frame[0].f_locals
+
+        base_name = f.__name__
+        for num, args in enumerate(input):
+            name_suffix = "_%s" % (num,)
+            if len(args) > 0 and isinstance(args[0], basestring):
+                name_suffix += "_" + to_safe_name(args[0])
+            name = base_name + name_suffix
+            new_func = parameterized_expand_helper(name, f, args)
+            frame_locals[name] = new_func
+        return skip_test(f)
+    return parameterized_expand_wrapper
+
+parameterized.expand = parameterized_expand
+
+
+def assert_contains(haystack, needle):
+    if needle not in haystack:
+        raise AssertionError("%r not in %r" % (needle, haystack))
+
+
+def assert_not_contains(haystack, needle):
+    if needle in haystack:
+        raise AssertionError("%r in %r" % (needle, haystack))
+
+
+def assert_raises(func, exc_type, str_contains=None, repr_contains=None):
+    try:
+        func()
+    except exc_type, e:
+        if str_contains is not None and str_contains not in str(e):
+            raise AssertionError("%s raised, but %r does not contain %r"
+                                 % (exc_type, str(e), str_contains))
+        if repr_contains is not None and repr_contains not in repr(e):
+            raise AssertionError("%s raised, but %r does not contain %r"
+                                 % (exc_type, repr(e), repr_contains))
+        return e
+    else:
+        raise AssertionError("%s not raised" % (exc_type,))
+
+
+log_handler = None
+
+
+def setup_logging():
+    """ Configures a log handler which will capure log messages during a test.
+        The ``logged_messages`` and ``assert_no_errors_logged`` functions can be
+        used to make assertions about these logged messages.
+
+        For example::
+
+            from ensi_common.testing import (
+                setup_logging, teardown_logging, assert_no_errors_logged,
+                assert_logged,
+            )
+
+            class TestWidget(object):
+                def setup(self):
+                    setup_logging()
+
+                def teardown(self):
+                    assert_no_errors_logged()
+                    teardown_logging()
+
+                def test_that_will_fail(self):
+                    log.warning("this warning message will trigger a failure")
+
+                def test_that_will_pass(self):
+                    log.info("but info messages are ok")
+                    assert_logged("info messages are ok")
+        """
+
+    global log_handler
+    if log_handler is not None:
+        logging.getLogger().removeHandler(log_handler)
+    log_handler = logging.handlers.BufferingHandler(1000)
+    formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
+    log_handler.setFormatter(formatter)
+    logging.getLogger().addHandler(log_handler)
+
+
+def teardown_logging():
+    global log_handler
+    if log_handler is not None:
+        logging.getLogger().removeHandler(log_handler)
+        log_handler = None
+
+
+def logged_messages():
+    assert log_handler, "setup_logging not called"
+    return [(log_handler.format(record), record) for record in log_handler.buffer]
+
+
+def assert_no_errors_logged():
+    for _, record in logged_messages():
+        if record.levelno >= logging.WARNING:
+            # Assume that the nose log capture plugin is being used, so it will
+            # show the exception.
+            raise AssertionError("an unexpected error was logged")
+
+
+def assert_logged(expected_msg_contents):
+    for msg, _ in logged_messages():
+        if expected_msg_contents in msg:
+            return
+    raise AssertionError("no logged message contains %r"
+                         % (expected_msg_contents,))