File parameterized-pr116-pytest4.patch of Package python-parameterized

From 674f23824328709562303941e9546097b360b4fc Mon Sep 17 00:00:00 2001
From: David Wolever <david@wolever.net>
Date: Sat, 9 Jan 2021 16:00:47 -0500
Subject: [PATCH 1/6] Enable pytest4 (tests will fail)

---
 tox.ini | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

Index: parameterized-0.8.1/tox.ini
===================================================================
--- parameterized-0.8.1.orig/tox.ini
+++ parameterized-0.8.1/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist=py{27,35,36,py}-{nose,nose2,pytest2,pytest3,unit,unit2},py{37,38,39}-{nose,nose2,pytest3,unit,unit2}
+envlist=py{27,35,36,py}-{nose,nose2,pytest2,pytest3,pytest4,unit,unit2},py{37,38,39}-{nose,nose2,pytest3,pytest4,unit,unit2}
 [testenv]
 deps=
     nose
@@ -7,13 +7,13 @@ deps=
     nose2: nose2
     pytest2: pytest>=2,<3
     pytest3: pytest>=3,<4
-    #pytest4: pytest>=4,<5
+    pytest4: pytest>=4,<5
     unit2: unittest2
 commands=
     nose: nosetests
     nose2: nose2
     pytest2: py.test parameterized/test.py
     pytest3: py.test parameterized/test.py
-    #pytest4: py.test parameterized/test.py
+    pytest4: py.test parameterized/test.py
     unit: python -m unittest parameterized.test
     unit2: unit2 parameterized.test
Index: parameterized-0.8.1/parameterized/parameterized.py
===================================================================
--- parameterized-0.8.1.orig/parameterized/parameterized.py
+++ parameterized-0.8.1/parameterized/parameterized.py
@@ -19,9 +19,14 @@ except ImportError:
     class SkipTest(Exception):
         pass
 
+try:
+    import pytest
+except ImportError:
+    pytest = None
+
 PY3 = sys.version_info[0] == 3
 PY2 = sys.version_info[0] == 2
-
+PYTEST4 = pytest and pytest.__version__ >= '4.0.0'
 
 if PY3:
     # Python 3 doesn't have an InstanceType, so just use a dummy type.
@@ -352,6 +357,120 @@ class parameterized(object):
     def __call__(self, test_func):
         self.assert_not_in_testcase_subclass()
 
+        input = self.get_input()
+        wrapper = self._wrap_test_func(test_func, input)
+        wrapper.parameterized_input = input
+        wrapper.parameterized_func = test_func
+        test_func.__name__ = "_parameterized_original_%s" %(test_func.__name__, )
+
+        return wrapper
+
+    def _wrap_test_func(self, test_func, input):
+        """ Wraps a test function so that it will appropriately handle
+            parameterization.
+
+            In the general case, the wrapper will enumerate the input, yielding
+            test cases.
+
+            In the case of pytest4, the wrapper will use
+            ``@pytest.mark.parametrize`` to parameterize the test function. """
+
+        if not input:
+            if not self.skip_on_empty:
+                raise ValueError(
+                    "Parameters iterable is empty (hint: use "
+                    "`parameterized([], skip_on_empty=True)` to skip "
+                    "this test when the input is empty)"
+                )
+            return wraps(test_func)(skip_on_empty_helper)
+
+        if PYTEST4 and detect_runner() == "pytest":
+            # pytest >= 4 compatibility is... a bit of work. Basically, the
+            # only way (I can find) of implementing parameterized testing with
+            # pytest >= 4 is through the ``@pytest.mark.parameterized``
+            # decorator. This decorator has some strange requirements around
+            # the name and number of arguments to the test function, so this
+            # wrapper works around that by:
+            # 1. Introspecting the original test function to determine the
+            #    names and default values of all arguments.
+            # 2. Creating a new function with the same arguments, but none
+            #    of them are optional::
+            #
+            #        def foo(a, b=42): ...
+            #
+            #    Becomes:
+            #
+            #        def parameterized_pytest_wrapper_foo(a, b): ...
+            #
+            # 3. Merging the ``@parameterized`` parameters with the argument
+            #    default values.
+            # 4. Generating a list of ``pytest.param(...)`` values, and passing
+            #    that into ``@pytest.mark.parameterized``.
+            # Some work also needs to be done to support the documented usage
+            # of ``mock.patch``, which also adds complexity.
+            Undefined = object()
+            test_func_wrapped = test_func
+            test_func_real, mock_patchings = unwrap_mock_patch_func(test_func_wrapped)
+            func_argspec = getargspec(test_func_real)
+
+            func_args = func_argspec.args
+            if mock_patchings:
+                func_args = func_args[:-len(mock_patchings)]
+
+            func_args_no_self = func_args
+            if func_args_no_self[:1] == ["self"]:
+                func_args_no_self = func_args_no_self[1:]
+
+            args_with_default = dict(
+                (arg, Undefined)
+                for arg in func_args_no_self
+            )
+            for (arg, default) in zip(reversed(func_args_no_self), reversed(func_argspec.defaults or [])):
+                args_with_default[arg] = default
+
+            pytest_params = []
+            for i in input:
+                p = dict(args_with_default)
+                for (arg, val) in zip(func_args_no_self, i.args):
+                    p[arg] = val
+                p.update(i.kwargs)
+
+                # Sanity check: all arguments should now be defined
+                if any(v is Undefined for v in p.values()):
+                    raise ValueError(
+                        "When parameterizing function %r: no value for "
+                        "arguments: %s with parameters %r "
+                        "(see: 'no value for arguments' in "
+                        "https://github.com/wolever/parameterized#faq)" %(
+                            test_func,
+                            ", ".join(
+                                repr(arg)
+                                for (arg, val) in p.items()
+                                if val is Undefined
+                            ),
+                            i,
+                        )
+                    )
+
+                pytest_params.append(pytest.param(*[
+                    p.get(arg) for arg in func_args_no_self
+                ]))
+
+            namespace = {
+                "__parameterized_original_test_func": test_func_wrapped,
+            }
+            wrapper_name = "parameterized_pytest_wrapper_%s" %(test_func.__name__, )
+            exec(
+                "def %s(%s, *__args): return __parameterized_original_test_func(%s, *__args)" %(
+                    wrapper_name,
+                    ",".join(func_args),
+                    ",".join(func_args),
+                ),
+                namespace,
+                namespace,
+            )
+            return pytest.mark.parametrize(",".join(func_args_no_self), pytest_params)(namespace[wrapper_name])
+
         @wraps(test_func)
         def wrapper(test_self=None):
             test_cls = test_self and type(test_self)
@@ -366,7 +485,7 @@ class parameterized(object):
                     ) %(test_self, ))
 
             original_doc = wrapper.__doc__
-            for num, args in enumerate(wrapper.parameterized_input):
+            for num, args in enumerate(input):
                 p = param.from_decorator(args)
                 unbound_func, nose_tuple = self.param_as_nose_tuple(test_self, test_func, num, p)
                 try:
@@ -383,21 +502,6 @@ class parameterized(object):
                     if test_self is not None:
                         delattr(test_cls, test_func.__name__)
                     wrapper.__doc__ = original_doc
-
-        input = self.get_input()
-        if not input:
-            if not self.skip_on_empty:
-                raise ValueError(
-                    "Parameters iterable is empty (hint: use "
-                    "`parameterized([], skip_on_empty=True)` to skip "
-                    "this test when the input is empty)"
-                )
-            wrapper = wraps(test_func)(skip_on_empty_helper)
-
-        wrapper.parameterized_input = input
-        wrapper.parameterized_func = test_func
-        test_func.__name__ = "_parameterized_original_%s" %(test_func.__name__, )
-
         return wrapper
 
     def param_as_nose_tuple(self, test_self, func, num, p):
@@ -618,6 +722,11 @@ def parameterized_class(attrs, input_val
 
     return decorator
 
+def unwrap_mock_patch_func(f):
+    if not hasattr(f, "patchings"):
+        return (f, [])
+    real_func, patchings = unwrap_mock_patch_func(f.__wrapped__)
+    return (real_func, patchings + f.patchings)
 
 def get_class_name_suffix(params_dict):
     if "name" in params_dict:
Index: parameterized-0.8.1/parameterized/test.py
===================================================================
--- parameterized-0.8.1.orig/parameterized/test.py
+++ parameterized-0.8.1/parameterized/test.py
@@ -6,8 +6,9 @@ from unittest import TestCase
 from nose.tools import assert_equal, assert_raises
 
 from .parameterized import (
-    PY3, PY2, parameterized, param, parameterized_argument_value_pairs,
-    short_repr, detect_runner, parameterized_class, SkipTest,
+    PY3, PY2, PYTEST4, parameterized, param,
+    parameterized_argument_value_pairs, short_repr, detect_runner,
+    parameterized_class, SkipTest,
 )
 
 def assert_contains(haystack, needle):
@@ -40,6 +41,7 @@ def expect(skip, tests=None):
 
 test_params = [
     (42, ),
+    (42, "bar_val"),
     "foo0",
     param("foo1"),
     param("foo2", bar=42),
@@ -50,6 +52,7 @@ expect("standalone", [
     "test_naked_function('foo1', bar=None)",
     "test_naked_function('foo2', bar=42)",
     "test_naked_function(42, bar=None)",
+    "test_naked_function(42, bar='bar_val')",
 ])
 
 @parameterized(test_params)
@@ -63,6 +66,7 @@ class TestParameterized(object):
         "test_instance_method('foo1', bar=None)",
         "test_instance_method('foo2', bar=42)",
         "test_instance_method(42, bar=None)",
+        "test_instance_method(42, bar='bar_val')",
     ])
 
     @parameterized(test_params)
@@ -95,10 +99,16 @@ if not PYTEST:
             missing_tests.remove("test_setup(%s)" %(self.actual_order, ))
 
 
-def custom_naming_func(custom_tag):
+def custom_naming_func(custom_tag, kw_name):
     def custom_naming_func(testcase_func, param_num, param):
-        return testcase_func.__name__ + ('_%s_name_' % custom_tag) + str(param.args[0])
-
+        return (
+            testcase_func.__name__ +
+            '_%s_name_' %(custom_tag, ) +
+            str(param.args[0]) + 
+            # This ... is a bit messy, to properly handle the values in
+            # `test_params`, but ... it should work.
+            '_%s' %(param.args[1] if len(param.args) > 1 else param.kwargs.get(kw_name), )
+        )
     return custom_naming_func
 
 
@@ -137,19 +147,20 @@ class TestParameterizedExpandWithMockPat
                               mock_fdopen._mock_name, mock_getpid._mock_name))
 
 
-@mock.patch("os.getpid")
-class TestParameterizedExpandWithNoExpand(object):
-    expect("generator", [
-        "test_patch_class_no_expand(42, 51, 'umask', 'getpid')",
-    ])
+if not (PYTEST4 and detect_runner() == 'pytest'):
+    @mock.patch("os.getpid")
+    class TestParameterizedExpandWithNoExpand(object):
+        expect("generator", [
+            "test_patch_class_no_expand(42, 51, 'umask', 'getpid')",
+        ])
 
-    @parameterized([(42, 51)])
-    @mock.patch("os.umask")
-    def test_patch_class_no_expand(self, foo, bar, mock_umask, mock_getpid):
-        missing_tests.remove("test_patch_class_no_expand"
-                             "(%r, %r, %r, %r)" %
-                             (foo, bar, mock_umask._mock_name,
-                              mock_getpid._mock_name))
+        @parameterized([(42, 51)])
+        @mock.patch("os.umask")
+        def test_patch_class_no_expand(self, foo, bar, mock_umask, mock_getpid):
+            missing_tests.remove("test_patch_class_no_expand"
+                                 "(%r, %r, %r, %r)" %
+                                 (foo, bar, mock_umask._mock_name,
+                                  mock_getpid._mock_name))
 
 
 class TestParameterizedExpandWithNoMockPatchForClass(TestCase):
@@ -214,6 +225,7 @@ class TestParamerizedOnTestCase(TestCase
         "test_on_TestCase('foo1', bar=None)",
         "test_on_TestCase('foo2', bar=42)",
         "test_on_TestCase(42, bar=None)",
+        "test_on_TestCase(42, bar='bar_val')",
     ])
 
     @parameterized.expand(test_params)
@@ -221,20 +233,21 @@ class TestParamerizedOnTestCase(TestCase
         missing_tests.remove("test_on_TestCase(%r, bar=%r)" %(foo, bar))
 
     expect([
-        "test_on_TestCase2_custom_name_42(42, bar=None)",
-        "test_on_TestCase2_custom_name_foo0('foo0', bar=None)",
-        "test_on_TestCase2_custom_name_foo1('foo1', bar=None)",
-        "test_on_TestCase2_custom_name_foo2('foo2', bar=42)",
+        "test_on_TestCase2_custom_name_42_None(42, bar=None)",
+        "test_on_TestCase2_custom_name_42_bar_val(42, bar='bar_val')",
+        "test_on_TestCase2_custom_name_foo0_None('foo0', bar=None)",
+        "test_on_TestCase2_custom_name_foo1_None('foo1', bar=None)",
+        "test_on_TestCase2_custom_name_foo2_42('foo2', bar=42)",
     ])
 
     @parameterized.expand(test_params,
-                          name_func=custom_naming_func("custom"))
+                          name_func=custom_naming_func("custom", "bar"))
     def test_on_TestCase2(self, foo, bar=None):
         stack = inspect.stack()
         frame = stack[1]
         frame_locals = frame[0].f_locals
         nose_test_method_name = frame_locals['a'][0]._testMethodName
-        expected_name = "test_on_TestCase2_custom_name_" + str(foo)
+        expected_name = "test_on_TestCase2_custom_name_" + str(foo) + "_" + str(bar)
         assert_equal(nose_test_method_name, expected_name,
                      "Test Method name '%s' did not get customized to expected: '%s'" %
                      (nose_test_method_name, expected_name))
@@ -373,6 +386,8 @@ def tearDownModule():
 def test_old_style_classes():
     if PY3:
         raise SkipTest("Py3 doesn't have old-style classes")
+    if PYTEST4 and detect_runner() == 'pytest':
+        raise SkipTest("We're not going to worry about old style classes with pytest 4")
     class OldStyleClass:
         @parameterized(["foo"])
         def parameterized_method(self, param):
@@ -552,3 +567,16 @@ class TestUnicodeDocstring(object):
     def test_with_docstring(self, param):
         """ Это док-стринг, содержащий не-ascii символы """
         pass
+
+if PYTEST4 and detect_runner() == 'pytest':
+    def test_missing_argument_error():
+        try:
+            @parameterized([
+                (1, ),
+            ])
+            def foo(a, b):
+                pass
+        except ValueError as e:
+            assert_contains(repr(e), "no value for arguments: 'b'")
+        else:
+            raise AssertionError("Expected exception not raised")
Index: parameterized-0.8.1/README.rst
===================================================================
--- parameterized-0.8.1.orig/README.rst
+++ parameterized-0.8.1/README.rst
@@ -9,11 +9,9 @@ Parameterized testing with any Python te
     :alt: Circle CI
     :target: https://circleci.com/gh/wolever/parameterized
 
-
-Parameterized testing in Python sucks.
-
-``parameterized`` fixes that. For everything. Parameterized testing for nose,
-parameterized testing for py.test, parameterized testing for unittest.
+``parameterized`` provides universal parameterized testing for Python:
+parameterized testing for nose, parameterized testing for py.test,
+parameterized testing for unittest, parameterized testing for Django.
 
 .. code:: python
 
@@ -131,7 +129,7 @@ With unittest (and unittest2)::
 (note: because unittest does not support test decorators, only tests created
 with ``@parameterized.expand`` will be executed)
 
-With green::
+With `green`__ ::
 
     $ green test_math.py -vvv
     test_math
@@ -161,6 +159,7 @@ With green::
 
     OK (passes=9)
 
+__ https://github.com/CleanCut/green
 
 Installation
 ------------
@@ -237,16 +236,16 @@ __ https://travis-ci.org/wolever/paramet
      - yes
      - yes
    * - py.test 4
-     - no**
-     - no**
-     - no**
-     - no**
-     - no**
-     - no**
-     - no**
-     - no**
-     - no**
-     - no**
+     - yes
+     - yes
+     - yes
+     - yes
+     - yes
+     - yes
+     - yes
+     - yes
+     - yes
+     - yes
    * - py.test fixtures
      - no†
      - no†
@@ -285,8 +284,6 @@ __ https://travis-ci.org/wolever/paramet
 
 \*: py.test 2 does `does not appear to work (#71)`__ under Python 3. Please comment on the related issues if you are affected.
 
-\*\*: py.test 4 is not yet supported (but coming!) in `issue #34`__
-
 †: py.test fixture support is documented in `issue #81`__
 
 __ https://github.com/wolever/parameterized/issues/71
@@ -575,7 +572,6 @@ which controls the name of the parameter
     test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok
 
 
-
 Using with Single Parameters
 ............................
 
@@ -616,15 +612,42 @@ can be confusing. The ``@mock.patch(...)
 
 .. code:: python
 
-   @mock.patch("os.getpid")
    class TestOS(object):
       @parameterized(...)
       @mock.patch("os.fdopen")
       @mock.patch("os.umask")
-      def test_method(self, param1, param2, ..., mock_umask, mock_fdopen, mock_getpid):
+      def test_method(self, param1, param2, ..., mock_umask, mock_fdopen):
          ...
 
-Note: the same holds true when using ``@parameterized.expand``.
+Note 1: the same holds true when using ``@parameterized.expand``.
+
+Note 2: ``@mock.patch`` is supported with all runners, including ``pytest``,
+*except* when used as a *class decorator* with ``pytest>=4``.
+
+Parameterized testing with Django
+.................................
+
+``parameterized`` enables parameterized testing with Django with 
+``@parameterized.expand``::
+
+    from django.test import TestCase
+
+    class DjangoTestCase(TestCase):
+        @parameterized.expand([
+            ("negative", -1.5, -2.0),
+            ("integer", 1, 1.0),
+            ("large fraction", 1.6, 1),
+        ])
+        def test_floor(self, name, input, expected):
+            assert_equal(math.floor(input), expected)
+
+Which will yield::
+
+    $ python manage.py test
+    ...
+    test_floor_0_negative (test_math.DjangoTestCase) ... ok
+    test_floor_1_integer (test_math.DjangoTestCase) ... ok
+    test_floor_2_large_fraction (test_math.DjangoTestCase) ... ok
 
 
 Migrating from ``nose-parameterized`` to ``parameterized``
@@ -650,7 +673,7 @@ What happened to ``nose-parameterized``?
     only made sense to change the name!
 
 What do you mean when you say "nose is best supported"?
-    There are small caveates with ``py.test`` and ``unittest``: ``py.test``
+    There are small caveats with ``py.test`` and ``unittest``: ``py.test``
     does not show the parameter values (ex, it will show ``test_add[0]``
     instead of ``test_add[1, 2, 3]``), and ``unittest``/``unittest2`` do not
     support test generators so ``@parameterized.expand`` must be used.
@@ -664,3 +687,26 @@ Why do I get an ``AttributeError: 'funct
     You've likely installed the ``parametrized`` (note the missing *e*)
     package. Use ``parameterized`` (with the *e*) instead and you'll be all
     set.
+
+What is the ``no value for arguments`` error when using ``pytest>=4``?
+    The ``no value for arguments`` error occurs with ``pytest>=4`` when the
+    parameters for a method do not supply values for all the test function
+    arguments.
+
+    For example, consider::
+
+        @parameterized([
+            (1, ),
+            (2, 3),
+        ])
+        def test_foo(a, b):
+            pass
+
+    In this case, the error will be ``no value for arguments: 'b' with 
+    paramters (1, )``, because the parameter ``(1, )`` does not provide
+    a value for the argument ``b``.
+
+    Because ``pytest.mark.parametrized`` - which is used to implement
+    parametrized testing with ``pytest>=4`` - depends fairly heavily on
+    argument names, this can also come up if other decorators are used (for
+    example, if ``@mock.patch`` is used as a class decorator).
openSUSE Build Service is sponsored by