summaryrefslogtreecommitdiffstats
path: root/contrib/python/parameterized
diff options
context:
space:
mode:
authornkozlovskiy <[email protected]>2023-09-29 12:24:06 +0300
committernkozlovskiy <[email protected]>2023-09-29 12:41:34 +0300
commite0e3e1717e3d33762ce61950504f9637a6e669ed (patch)
treebca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/parameterized
parent38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff)
add ydb deps
Diffstat (limited to 'contrib/python/parameterized')
-rw-r--r--contrib/python/parameterized/py2/LICENSE.txt27
-rw-r--r--contrib/python/parameterized/py2/README.rst666
-rw-r--r--contrib/python/parameterized/py3/.dist-info/METADATA669
-rw-r--r--contrib/python/parameterized/py3/.dist-info/top_level.txt1
-rw-r--r--contrib/python/parameterized/py3/LICENSE.txt27
-rw-r--r--contrib/python/parameterized/py3/README.rst654
-rw-r--r--contrib/python/parameterized/py3/parameterized/__init__.py3
-rw-r--r--contrib/python/parameterized/py3/parameterized/parameterized.py732
-rw-r--r--contrib/python/parameterized/py3/ya.make23
-rw-r--r--contrib/python/parameterized/ya.make18
10 files changed, 2820 insertions, 0 deletions
diff --git a/contrib/python/parameterized/py2/LICENSE.txt b/contrib/python/parameterized/py2/LICENSE.txt
new file mode 100644
index 00000000000..e58a36e52d8
--- /dev/null
+++ b/contrib/python/parameterized/py2/LICENSE.txt
@@ -0,0 +1,27 @@
+Unless stated otherwise in the source files, all code is copyright 2010 David
+Wolever <[email protected]>. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY DAVID WOLEVER ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL DAVID WOLEVER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of David Wolever.
diff --git a/contrib/python/parameterized/py2/README.rst b/contrib/python/parameterized/py2/README.rst
new file mode 100644
index 00000000000..4da8c05f165
--- /dev/null
+++ b/contrib/python/parameterized/py2/README.rst
@@ -0,0 +1,666 @@
+Parameterized testing with any Python test framework
+====================================================
+
+.. image:: https://img.shields.io/pypi/v/parameterized.svg
+ :alt: PyPI
+ :target: https://pypi.org/project/parameterized/
+
+.. image:: https://circleci.com/gh/wolever/parameterized.svg?style=svg
+ :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.
+
+.. code:: python
+
+ # test_math.py
+ from nose.tools import assert_equal
+ from parameterized import parameterized, parameterized_class
+
+ import unittest
+ import math
+
+ @parameterized([
+ (2, 2, 4),
+ (2, 3, 8),
+ (1, 9, 1),
+ (0, 9, 0),
+ ])
+ def test_pow(base, exponent, expected):
+ assert_equal(math.pow(base, exponent), expected)
+
+ class TestMathUnitTest(unittest.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)
+
+ @parameterized_class(('a', 'b', 'expected_sum', 'expected_product'), [
+ (1, 2, 3, 2),
+ (5, 5, 10, 25),
+ ])
+ class TestMathClass(unittest.TestCase):
+ def test_add(self):
+ assert_equal(self.a + self.b, self.expected_sum)
+
+ def test_multiply(self):
+ assert_equal(self.a * self.b, self.expected_product)
+
+ @parameterized_class([
+ { "a": 3, "expected": 2 },
+ { "b": 5, "expected": -4 },
+ ])
+ class TestMathClassDict(unittest.TestCase):
+ a = 1
+ b = 1
+
+ def test_subtract(self):
+ assert_equal(self.a - self.b, self.expected)
+
+
+With nose (and nose2)::
+
+ $ nosetests -v test_math.py
+ test_floor_0_negative (test_math.TestMathUnitTest) ... ok
+ test_floor_1_integer (test_math.TestMathUnitTest) ... ok
+ test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
+ test_math.test_pow(2, 2, 4, {}) ... ok
+ test_math.test_pow(2, 3, 8, {}) ... ok
+ test_math.test_pow(1, 9, 1, {}) ... ok
+ test_math.test_pow(0, 9, 0, {}) ... ok
+ test_add (test_math.TestMathClass_0) ... ok
+ test_multiply (test_math.TestMathClass_0) ... ok
+ test_add (test_math.TestMathClass_1) ... ok
+ test_multiply (test_math.TestMathClass_1) ... ok
+ test_subtract (test_math.TestMathClassDict_0) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 12 tests in 0.015s
+
+ OK
+
+As the package name suggests, nose is best supported and will be used for all
+further examples.
+
+
+With py.test (version 2.0 and above)::
+
+ $ py.test -v test_math.py
+ ============================= test session starts ==============================
+ platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
+ collecting ... collected 13 items
+
+ test_math.py::test_pow::[0] PASSED
+ test_math.py::test_pow::[1] PASSED
+ test_math.py::test_pow::[2] PASSED
+ test_math.py::test_pow::[3] PASSED
+ test_math.py::TestMathUnitTest::test_floor_0_negative PASSED
+ test_math.py::TestMathUnitTest::test_floor_1_integer PASSED
+ test_math.py::TestMathUnitTest::test_floor_2_large_fraction PASSED
+ test_math.py::TestMathClass_0::test_add PASSED
+ test_math.py::TestMathClass_0::test_multiply PASSED
+ test_math.py::TestMathClass_1::test_add PASSED
+ test_math.py::TestMathClass_1::test_multiply PASSED
+ test_math.py::TestMathClassDict_0::test_subtract PASSED
+ ==================== 12 passed, 4 warnings in 0.16 seconds =====================
+
+With unittest (and unittest2)::
+
+ $ python -m unittest -v test_math
+ test_floor_0_negative (test_math.TestMathUnitTest) ... ok
+ test_floor_1_integer (test_math.TestMathUnitTest) ... ok
+ test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
+ test_add (test_math.TestMathClass_0) ... ok
+ test_multiply (test_math.TestMathClass_0) ... ok
+ test_add (test_math.TestMathClass_1) ... ok
+ test_multiply (test_math.TestMathClass_1) ... ok
+ test_subtract (test_math.TestMathClassDict_0) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 8 tests in 0.001s
+
+ OK
+
+(note: because unittest does not support test decorators, only tests created
+with ``@parameterized.expand`` will be executed)
+
+With green::
+
+ $ green test_math.py -vvv
+ test_math
+ TestMathClass_1
+ . test_method_a
+ . test_method_b
+ TestMathClass_2
+ . test_method_a
+ . test_method_b
+ TestMathClass_3
+ . test_method_a
+ . test_method_b
+ TestMathUnitTest
+ . test_floor_0_negative
+ . test_floor_1_integer
+ . test_floor_2_large_fraction
+ TestMathClass_0
+ . test_add
+ . test_multiply
+ TestMathClass_1
+ . test_add
+ . test_multiply
+ TestMathClassDict_0
+ . test_subtract
+
+ Ran 12 tests in 0.121s
+
+ OK (passes=9)
+
+
+Installation
+------------
+
+::
+
+ $ pip install parameterized
+
+
+Compatibility
+-------------
+
+`Yes`__ (mostly).
+
+__ https://travis-ci.org/wolever/parameterized
+
+.. list-table::
+ :header-rows: 1
+ :stub-columns: 1
+
+ * -
+ - Py2.6
+ - Py2.7
+ - Py3.4
+ - Py3.5
+ - Py3.6
+ - Py3.7
+ - Py3.8
+ - Py3.9
+ - PyPy
+ - ``@mock.patch``
+ * - nose
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ * - nose2
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ * - py.test 2
+ - yes
+ - yes
+ - no*
+ - no*
+ - no*
+ - no*
+ - yes
+ - yes
+ - yes
+ - yes
+ * - py.test 3
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ * - py.test 4
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ * - py.test fixtures
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ * - | unittest
+ | (``@parameterized.expand``)
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ * - | unittest2
+ | (``@parameterized.expand``)
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+
+\*: 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
+__ https://github.com/wolever/parameterized/issues/34
+__ https://github.com/wolever/parameterized/issues/81
+
+Dependencies
+------------
+
+(this section left intentionally blank)
+
+
+Exhaustive Usage Examples
+--------------------------
+
+The ``@parameterized`` and ``@parameterized.expand`` decorators accept a list
+or iterable of tuples or ``param(...)``, or a callable which returns a list or
+iterable:
+
+.. code:: python
+
+ from parameterized import parameterized, param
+
+ # A list of tuples
+ @parameterized([
+ (2, 3, 5),
+ (3, 5, 8),
+ ])
+ def test_add(a, b, expected):
+ assert_equal(a + b, expected)
+
+ # A list of params
+ @parameterized([
+ param("10", 10),
+ param("10", 16, base=16),
+ ])
+ def test_int(str_val, expected, base=10):
+ assert_equal(int(str_val, base=base), expected)
+
+ # An iterable of params
+ @parameterized(
+ param.explicit(*json.loads(line))
+ for line in open("testcases.jsons")
+ )
+ def test_from_json_file(...):
+ ...
+
+ # A callable which returns a list of tuples
+ def load_test_cases():
+ return [
+ ("test1", ),
+ ("test2", ),
+ ]
+ @parameterized(load_test_cases)
+ def test_from_function(name):
+ ...
+
+.. **
+
+Note that, when using an iterator or a generator, all the items will be loaded
+into memory before the start of the test run (we do this explicitly to ensure
+that generators are exhausted exactly once in multi-process or multi-threaded
+testing environments).
+
+The ``@parameterized`` decorator can be used test class methods, and standalone
+functions:
+
+.. code:: python
+
+ from parameterized import parameterized
+
+ class AddTest(object):
+ @parameterized([
+ (2, 3, 5),
+ ])
+ def test_add(self, a, b, expected):
+ assert_equal(a + b, expected)
+
+ @parameterized([
+ (2, 3, 5),
+ ])
+ def test_add(a, b, expected):
+ assert_equal(a + b, expected)
+
+
+And ``@parameterized.expand`` can be used to generate test methods in
+situations where test generators cannot be used (for example, when the test
+class is a subclass of ``unittest.TestCase``):
+
+.. code:: python
+
+ import unittest
+ from parameterized import parameterized
+
+ class AddTestCase(unittest.TestCase):
+ @parameterized.expand([
+ ("2 and 3", 2, 3, 5),
+ ("3 and 5", 2, 3, 5),
+ ])
+ def test_add(self, _, a, b, expected):
+ assert_equal(a + b, expected)
+
+Will create the test cases::
+
+ $ nosetests example.py
+ test_add_0_2_and_3 (example.AddTestCase) ... ok
+ test_add_1_3_and_5 (example.AddTestCase) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 2 tests in 0.001s
+
+ OK
+
+Note that ``@parameterized.expand`` works by creating new methods on the test
+class. If the first parameter is a string, that string will be added to the end
+of the method name. For example, the test case above will generate the methods
+``test_add_0_2_and_3`` and ``test_add_1_3_and_5``.
+
+The names of the test cases generated by ``@parameterized.expand`` can be
+customized using the ``name_func`` keyword argument. The value should
+be a function which accepts three arguments: ``testcase_func``, ``param_num``,
+and ``params``, and it should return the name of the test case.
+``testcase_func`` will be the function to be tested, ``param_num`` will be the
+index of the test case parameters in the list of parameters, and ``param``
+(an instance of ``param``) will be the parameters which will be used.
+
+.. code:: python
+
+ import unittest
+ from parameterized import parameterized
+
+ def custom_name_func(testcase_func, param_num, param):
+ return "%s_%s" %(
+ testcase_func.__name__,
+ parameterized.to_safe_name("_".join(str(x) for x in param.args)),
+ )
+
+ class AddTestCase(unittest.TestCase):
+ @parameterized.expand([
+ (2, 3, 5),
+ (2, 3, 5),
+ ], name_func=custom_name_func)
+ def test_add(self, a, b, expected):
+ assert_equal(a + b, expected)
+
+Will create the test cases::
+
+ $ nosetests example.py
+ test_add_1_2_3 (example.AddTestCase) ... ok
+ test_add_2_3_5 (example.AddTestCase) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 2 tests in 0.001s
+
+ OK
+
+
+The ``param(...)`` helper class stores the parameters for one specific test
+case. It can be used to pass keyword arguments to test cases:
+
+.. code:: python
+
+ from parameterized import parameterized, param
+
+ @parameterized([
+ param("10", 10),
+ param("10", 16, base=16),
+ ])
+ def test_int(str_val, expected, base=10):
+ assert_equal(int(str_val, base=base), expected)
+
+
+If test cases have a docstring, the parameters for that test case will be
+appended to the first line of the docstring. This behavior can be controlled
+with the ``doc_func`` argument:
+
+.. code:: python
+
+ from parameterized import parameterized
+
+ @parameterized([
+ (1, 2, 3),
+ (4, 5, 9),
+ ])
+ def test_add(a, b, expected):
+ """ Test addition. """
+ assert_equal(a + b, expected)
+
+ def my_doc_func(func, num, param):
+ return "%s: %s with %s" %(num, func.__name__, param)
+
+ @parameterized([
+ (5, 4, 1),
+ (9, 6, 3),
+ ], doc_func=my_doc_func)
+ def test_subtraction(a, b, expected):
+ assert_equal(a - b, expected)
+
+::
+
+ $ nosetests example.py
+ Test addition. [with a=1, b=2, expected=3] ... ok
+ Test addition. [with a=4, b=5, expected=9] ... ok
+ 0: test_subtraction with param(*(5, 4, 1)) ... ok
+ 1: test_subtraction with param(*(9, 6, 3)) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 4 tests in 0.001s
+
+ OK
+
+Finally ``@parameterized_class`` parameterizes an entire class, using
+either a list of attributes, or a list of dicts that will be applied to the
+class:
+
+.. code:: python
+
+ from yourapp.models import User
+ from parameterized import parameterized_class
+
+ @parameterized_class([
+ { "username": "user_1", "access_level": 1 },
+ { "username": "user_2", "access_level": 2, "expected_status_code": 404 },
+ ])
+ class TestUserAccessLevel(TestCase):
+ expected_status_code = 200
+
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
+
+ def test_url_a(self):
+ response = self.client.get('/url')
+ self.assertEqual(response.status_code, self.expected_status_code)
+
+ def tearDown(self):
+ self.client.logout()
+
+
+ @parameterized_class(("username", "access_level", "expected_status_code"), [
+ ("user_1", 1, 200),
+ ("user_2", 2, 404)
+ ])
+ class TestUserAccessLevel(TestCase):
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
+
+ def test_url_a(self):
+ response = self.client.get("/url")
+ self.assertEqual(response.status_code, self.expected_status_code)
+
+ def tearDown(self):
+ self.client.logout()
+
+
+The ``@parameterized_class`` decorator accepts a ``class_name_func`` argument,
+which controls the name of the parameterized classes generated by
+``@parameterized_class``:
+
+.. code:: python
+
+ from parameterized import parameterized, parameterized_class
+
+ def get_class_name(cls, num, params_dict):
+ # By default the generated class named includes either the "name"
+ # parameter (if present), or the first string value. This example shows
+ # multiple parameters being included in the generated class name:
+ return "%s_%s_%s%s" %(
+ cls.__name__,
+ num,
+ parameterized.to_safe_name(params_dict['a']),
+ parameterized.to_safe_name(params_dict['b']),
+ )
+
+ @parameterized_class([
+ { "a": "hello", "b": " world!", "expected": "hello world!" },
+ { "a": "say ", "b": " cheese :)", "expected": "say cheese :)" },
+ ], class_name_func=get_class_name)
+ class TestConcatenation(TestCase):
+ def test_concat(self):
+ self.assertEqual(self.a + self.b, self.expected)
+
+::
+
+ $ nosetests -v test_math.py
+ test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok
+ test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok
+
+
+
+Using with Single Parameters
+............................
+
+If a test function only accepts one parameter and the value is not iterable,
+then it is possible to supply a list of values without wrapping each one in a
+tuple:
+
+.. code:: python
+
+ @parameterized([1, 2, 3])
+ def test_greater_than_zero(value):
+ assert value > 0
+
+Note, however, that if the single parameter *is* iterable (such as a list or
+tuple), then it *must* be wrapped in a tuple, list, or the ``param(...)``
+helper:
+
+.. code:: python
+
+ @parameterized([
+ ([1, 2, 3], ),
+ ([3, 3], ),
+ ([6], ),
+ ])
+ def test_sums_to_6(numbers):
+ assert sum(numbers) == 6
+
+(note, also, that Python requires single element tuples to be defined with a
+trailing comma: ``(foo, )``)
+
+
+Using with ``@mock.patch``
+..........................
+
+``parameterized`` can be used with ``mock.patch``, but the argument ordering
+can be confusing. The ``@mock.patch(...)`` decorator must come *below* the
+``@parameterized(...)``, and the mocked parameters must come *last*:
+
+.. 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):
+ ...
+
+Note: the same holds true when using ``@parameterized.expand``.
+
+
+Migrating from ``nose-parameterized`` to ``parameterized``
+----------------------------------------------------------
+
+To migrate a codebase from ``nose-parameterized`` to ``parameterized``:
+
+1. Update your requirements file, replacing ``nose-parameterized`` with
+ ``parameterized``.
+
+2. Replace all references to ``nose_parameterized`` with ``parameterized``::
+
+ $ perl -pi -e 's/nose_parameterized/parameterized/g' your-codebase/
+
+3. You're done!
+
+
+FAQ
+---
+
+What happened to ``nose-parameterized``?
+ Originally only nose was supported. But now everything is supported, and it
+ 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``
+ 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.
+
+Why not use ``@pytest.mark.parametrize``?
+ Because spelling is difficult. Also, ``parameterized`` doesn't require you
+ to repeat argument names, and (using ``param``) it supports optional
+ keyword arguments.
+
+Why do I get an ``AttributeError: 'function' object has no attribute 'expand'`` with ``@parameterized.expand``?
+ You've likely installed the ``parametrized`` (note the missing *e*)
+ package. Use ``parameterized`` (with the *e*) instead and you'll be all
+ set.
diff --git a/contrib/python/parameterized/py3/.dist-info/METADATA b/contrib/python/parameterized/py3/.dist-info/METADATA
new file mode 100644
index 00000000000..531c64fee64
--- /dev/null
+++ b/contrib/python/parameterized/py3/.dist-info/METADATA
@@ -0,0 +1,669 @@
+Metadata-Version: 2.1
+Name: parameterized
+Version: 0.9.0
+Summary: Parameterized testing with any Python test framework
+Author-email: David Wolever <[email protected]>
+License: FreeBSD
+Project-URL: Homepage, https://github.com/wolever/parameterized
+Classifier: Programming Language :: Python :: 3
+Classifier: License :: OSI Approved :: BSD License
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE.txt
+Provides-Extra: dev
+Requires-Dist: jinja2 ; extra == 'dev'
+
+Parameterized testing with any Python test framework
+====================================================
+
+.. image:: https://img.shields.io/pypi/v/parameterized
+ :alt: PyPI
+ :target: https://pypi.org/project/parameterized/
+
+.. image:: https://img.shields.io/pypi/dm/parameterized
+ :alt: PyPI - Downloads
+ :target: https://pypi.org/project/parameterized/
+
+.. image:: https://circleci.com/gh/wolever/parameterized.svg?style=svg
+ :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.
+
+.. code:: python
+
+ # test_math.py
+ from nose.tools import assert_equal
+ from parameterized import parameterized, parameterized_class
+
+ import unittest
+ import math
+
+ @parameterized([
+ (2, 2, 4),
+ (2, 3, 8),
+ (1, 9, 1),
+ (0, 9, 0),
+ ])
+ def test_pow(base, exponent, expected):
+ assert_equal(math.pow(base, exponent), expected)
+
+ class TestMathUnitTest(unittest.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)
+
+ @parameterized_class(('a', 'b', 'expected_sum', 'expected_product'), [
+ (1, 2, 3, 2),
+ (5, 5, 10, 25),
+ ])
+ class TestMathClass(unittest.TestCase):
+ def test_add(self):
+ assert_equal(self.a + self.b, self.expected_sum)
+
+ def test_multiply(self):
+ assert_equal(self.a * self.b, self.expected_product)
+
+ @parameterized_class([
+ { "a": 3, "expected": 2 },
+ { "b": 5, "expected": -4 },
+ ])
+ class TestMathClassDict(unittest.TestCase):
+ a = 1
+ b = 1
+
+ def test_subtract(self):
+ assert_equal(self.a - self.b, self.expected)
+
+
+With nose (and nose2)::
+
+ $ nosetests -v test_math.py
+ test_floor_0_negative (test_math.TestMathUnitTest) ... ok
+ test_floor_1_integer (test_math.TestMathUnitTest) ... ok
+ test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
+ test_math.test_pow(2, 2, 4, {}) ... ok
+ test_math.test_pow(2, 3, 8, {}) ... ok
+ test_math.test_pow(1, 9, 1, {}) ... ok
+ test_math.test_pow(0, 9, 0, {}) ... ok
+ test_add (test_math.TestMathClass_0) ... ok
+ test_multiply (test_math.TestMathClass_0) ... ok
+ test_add (test_math.TestMathClass_1) ... ok
+ test_multiply (test_math.TestMathClass_1) ... ok
+ test_subtract (test_math.TestMathClassDict_0) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 12 tests in 0.015s
+
+ OK
+
+As the package name suggests, nose is best supported and will be used for all
+further examples.
+
+
+With py.test (version 2.0 and above)::
+
+ $ py.test -v test_math.py
+ ============================= test session starts ==============================
+ platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
+ collecting ... collected 13 items
+
+ test_math.py::test_pow::[0] PASSED
+ test_math.py::test_pow::[1] PASSED
+ test_math.py::test_pow::[2] PASSED
+ test_math.py::test_pow::[3] PASSED
+ test_math.py::TestMathUnitTest::test_floor_0_negative PASSED
+ test_math.py::TestMathUnitTest::test_floor_1_integer PASSED
+ test_math.py::TestMathUnitTest::test_floor_2_large_fraction PASSED
+ test_math.py::TestMathClass_0::test_add PASSED
+ test_math.py::TestMathClass_0::test_multiply PASSED
+ test_math.py::TestMathClass_1::test_add PASSED
+ test_math.py::TestMathClass_1::test_multiply PASSED
+ test_math.py::TestMathClassDict_0::test_subtract PASSED
+ ==================== 12 passed, 4 warnings in 0.16 seconds =====================
+
+With unittest (and unittest2)::
+
+ $ python -m unittest -v test_math
+ test_floor_0_negative (test_math.TestMathUnitTest) ... ok
+ test_floor_1_integer (test_math.TestMathUnitTest) ... ok
+ test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
+ test_add (test_math.TestMathClass_0) ... ok
+ test_multiply (test_math.TestMathClass_0) ... ok
+ test_add (test_math.TestMathClass_1) ... ok
+ test_multiply (test_math.TestMathClass_1) ... ok
+ test_subtract (test_math.TestMathClassDict_0) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 8 tests in 0.001s
+
+ OK
+
+(note: because unittest does not support test decorators, only tests created
+with ``@parameterized.expand`` will be executed)
+
+With green::
+
+ $ green test_math.py -vvv
+ test_math
+ TestMathClass_1
+ . test_method_a
+ . test_method_b
+ TestMathClass_2
+ . test_method_a
+ . test_method_b
+ TestMathClass_3
+ . test_method_a
+ . test_method_b
+ TestMathUnitTest
+ . test_floor_0_negative
+ . test_floor_1_integer
+ . test_floor_2_large_fraction
+ TestMathClass_0
+ . test_add
+ . test_multiply
+ TestMathClass_1
+ . test_add
+ . test_multiply
+ TestMathClassDict_0
+ . test_subtract
+
+ Ran 12 tests in 0.121s
+
+ OK (passes=9)
+
+
+Installation
+------------
+
+::
+
+ $ pip install parameterized
+
+
+Compatibility
+-------------
+
+`Yes`__ (mostly).
+
+__ https://app.circleci.com/pipelines/github/wolever/parameterized?branch=master
+
+.. list-table::
+ :header-rows: 1
+ :stub-columns: 1
+
+ * -
+ - Py3.7
+ - Py3.8
+ - Py3.9
+ - Py3.10
+ - Py3.11
+ - PyPy3
+ - ``@mock.patch``
+ * - nose
+ - yes
+ - yes
+ - yes
+ - yes
+ - no§
+ - no§
+ - yes
+ * - nose2
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ * - py.test 2
+ - no*
+ - no*
+ - no*
+ - no*
+ - no*
+ - no*
+ - no*
+ * - py.test 3
+ - yes
+ - yes
+ - yes
+ - yes
+ - no*
+ - no*
+ - yes
+ * - py.test 4
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ * - py.test fixtures
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ * - | unittest
+ | (``@parameterized.expand``)
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ * - | unittest2
+ | (``@parameterized.expand``)
+ - yes
+ - yes
+ - yes
+ - yes
+ - no§
+ - no§
+ - yes
+
+§: nose and unittest2 - both of which were last updated in 2015 - sadly do not
+appear to support Python 3.10 or 3.11.
+
+\*: `py.test 2 does not appear to work under Python 3 (#71)`__, and
+`py.test 3 does not appear to work under Python 3.10 or 3.11 (#154)`__.
+
+\*\*: 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
+__ https://github.com/wolever/parameterized/issues/154
+__ https://github.com/wolever/parameterized/issues/34
+__ https://github.com/wolever/parameterized/issues/81
+
+Dependencies
+------------
+
+(this section left intentionally blank)
+
+
+Exhaustive Usage Examples
+--------------------------
+
+The ``@parameterized`` and ``@parameterized.expand`` decorators accept a list
+or iterable of tuples or ``param(...)``, or a callable which returns a list or
+iterable:
+
+.. code:: python
+
+ from parameterized import parameterized, param
+
+ # A list of tuples
+ @parameterized([
+ (2, 3, 5),
+ (3, 5, 8),
+ ])
+ def test_add(a, b, expected):
+ assert_equal(a + b, expected)
+
+ # A list of params
+ @parameterized([
+ param("10", 10),
+ param("10", 16, base=16),
+ ])
+ def test_int(str_val, expected, base=10):
+ assert_equal(int(str_val, base=base), expected)
+
+ # An iterable of params
+ @parameterized(
+ param.explicit(*json.loads(line))
+ for line in open("testcases.jsons")
+ )
+ def test_from_json_file(...):
+ ...
+
+ # A callable which returns a list of tuples
+ def load_test_cases():
+ return [
+ ("test1", ),
+ ("test2", ),
+ ]
+ @parameterized(load_test_cases)
+ def test_from_function(name):
+ ...
+
+.. **
+
+Note that, when using an iterator or a generator, all the items will be loaded
+into memory before the start of the test run (we do this explicitly to ensure
+that generators are exhausted exactly once in multi-process or multi-threaded
+testing environments).
+
+The ``@parameterized`` decorator can be used test class methods, and standalone
+functions:
+
+.. code:: python
+
+ from parameterized import parameterized
+
+ class AddTest(object):
+ @parameterized([
+ (2, 3, 5),
+ ])
+ def test_add(self, a, b, expected):
+ assert_equal(a + b, expected)
+
+ @parameterized([
+ (2, 3, 5),
+ ])
+ def test_add(a, b, expected):
+ assert_equal(a + b, expected)
+
+
+And ``@parameterized.expand`` can be used to generate test methods in
+situations where test generators cannot be used (for example, when the test
+class is a subclass of ``unittest.TestCase``):
+
+.. code:: python
+
+ import unittest
+ from parameterized import parameterized
+
+ class AddTestCase(unittest.TestCase):
+ @parameterized.expand([
+ ("2 and 3", 2, 3, 5),
+ ("3 and 5", 3, 5, 8),
+ ])
+ def test_add(self, _, a, b, expected):
+ assert_equal(a + b, expected)
+
+Will create the test cases::
+
+ $ nosetests example.py
+ test_add_0_2_and_3 (example.AddTestCase) ... ok
+ test_add_1_3_and_5 (example.AddTestCase) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 2 tests in 0.001s
+
+ OK
+
+Note that ``@parameterized.expand`` works by creating new methods on the test
+class. If the first parameter is a string, that string will be added to the end
+of the method name. For example, the test case above will generate the methods
+``test_add_0_2_and_3`` and ``test_add_1_3_and_5``.
+
+The names of the test cases generated by ``@parameterized.expand`` can be
+customized using the ``name_func`` keyword argument. The value should
+be a function which accepts three arguments: ``testcase_func``, ``param_num``,
+and ``params``, and it should return the name of the test case.
+``testcase_func`` will be the function to be tested, ``param_num`` will be the
+index of the test case parameters in the list of parameters, and ``param``
+(an instance of ``param``) will be the parameters which will be used.
+
+.. code:: python
+
+ import unittest
+ from parameterized import parameterized
+
+ def custom_name_func(testcase_func, param_num, param):
+ return "%s_%s" %(
+ testcase_func.__name__,
+ parameterized.to_safe_name("_".join(str(x) for x in param.args)),
+ )
+
+ class AddTestCase(unittest.TestCase):
+ @parameterized.expand([
+ (2, 3, 5),
+ (2, 3, 5),
+ ], name_func=custom_name_func)
+ def test_add(self, a, b, expected):
+ assert_equal(a + b, expected)
+
+Will create the test cases::
+
+ $ nosetests example.py
+ test_add_1_2_3 (example.AddTestCase) ... ok
+ test_add_2_3_5 (example.AddTestCase) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 2 tests in 0.001s
+
+ OK
+
+
+The ``param(...)`` helper class stores the parameters for one specific test
+case. It can be used to pass keyword arguments to test cases:
+
+.. code:: python
+
+ from parameterized import parameterized, param
+
+ @parameterized([
+ param("10", 10),
+ param("10", 16, base=16),
+ ])
+ def test_int(str_val, expected, base=10):
+ assert_equal(int(str_val, base=base), expected)
+
+
+If test cases have a docstring, the parameters for that test case will be
+appended to the first line of the docstring. This behavior can be controlled
+with the ``doc_func`` argument:
+
+.. code:: python
+
+ from parameterized import parameterized
+
+ @parameterized([
+ (1, 2, 3),
+ (4, 5, 9),
+ ])
+ def test_add(a, b, expected):
+ """ Test addition. """
+ assert_equal(a + b, expected)
+
+ def my_doc_func(func, num, param):
+ return "%s: %s with %s" %(num, func.__name__, param)
+
+ @parameterized([
+ (5, 4, 1),
+ (9, 6, 3),
+ ], doc_func=my_doc_func)
+ def test_subtraction(a, b, expected):
+ assert_equal(a - b, expected)
+
+::
+
+ $ nosetests example.py
+ Test addition. [with a=1, b=2, expected=3] ... ok
+ Test addition. [with a=4, b=5, expected=9] ... ok
+ 0: test_subtraction with param(*(5, 4, 1)) ... ok
+ 1: test_subtraction with param(*(9, 6, 3)) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 4 tests in 0.001s
+
+ OK
+
+Finally ``@parameterized_class`` parameterizes an entire class, using
+either a list of attributes, or a list of dicts that will be applied to the
+class:
+
+.. code:: python
+
+ from yourapp.models import User
+ from parameterized import parameterized_class
+
+ @parameterized_class([
+ { "username": "user_1", "access_level": 1 },
+ { "username": "user_2", "access_level": 2, "expected_status_code": 404 },
+ ])
+ class TestUserAccessLevel(TestCase):
+ expected_status_code = 200
+
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
+
+ def test_url_a(self):
+ response = self.client.get('/url')
+ self.assertEqual(response.status_code, self.expected_status_code)
+
+ def tearDown(self):
+ self.client.logout()
+
+
+ @parameterized_class(("username", "access_level", "expected_status_code"), [
+ ("user_1", 1, 200),
+ ("user_2", 2, 404)
+ ])
+ class TestUserAccessLevel(TestCase):
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
+
+ def test_url_a(self):
+ response = self.client.get("/url")
+ self.assertEqual(response.status_code, self.expected_status_code)
+
+ def tearDown(self):
+ self.client.logout()
+
+
+The ``@parameterized_class`` decorator accepts a ``class_name_func`` argument,
+which controls the name of the parameterized classes generated by
+``@parameterized_class``:
+
+.. code:: python
+
+ from parameterized import parameterized, parameterized_class
+
+ def get_class_name(cls, num, params_dict):
+ # By default the generated class named includes either the "name"
+ # parameter (if present), or the first string value. This example shows
+ # multiple parameters being included in the generated class name:
+ return "%s_%s_%s%s" %(
+ cls.__name__,
+ num,
+ parameterized.to_safe_name(params_dict['a']),
+ parameterized.to_safe_name(params_dict['b']),
+ )
+
+ @parameterized_class([
+ { "a": "hello", "b": " world!", "expected": "hello world!" },
+ { "a": "say ", "b": " cheese :)", "expected": "say cheese :)" },
+ ], class_name_func=get_class_name)
+ class TestConcatenation(TestCase):
+ def test_concat(self):
+ self.assertEqual(self.a + self.b, self.expected)
+
+::
+
+ $ nosetests -v test_math.py
+ test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok
+ test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok
+
+
+
+Using with Single Parameters
+............................
+
+If a test function only accepts one parameter and the value is not iterable,
+then it is possible to supply a list of values without wrapping each one in a
+tuple:
+
+.. code:: python
+
+ @parameterized([1, 2, 3])
+ def test_greater_than_zero(value):
+ assert value > 0
+
+Note, however, that if the single parameter *is* iterable (such as a list or
+tuple), then it *must* be wrapped in a tuple, list, or the ``param(...)``
+helper:
+
+.. code:: python
+
+ @parameterized([
+ ([1, 2, 3], ),
+ ([3, 3], ),
+ ([6], ),
+ ])
+ def test_sums_to_6(numbers):
+ assert sum(numbers) == 6
+
+(note, also, that Python requires single element tuples to be defined with a
+trailing comma: ``(foo, )``)
+
+
+Using with ``@mock.patch``
+..........................
+
+``parameterized`` can be used with ``mock.patch``, but the argument ordering
+can be confusing. The ``@mock.patch(...)`` decorator must come *below* the
+``@parameterized(...)``, and the mocked parameters must come *last*:
+
+.. 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):
+ ...
+
+Note: the same holds true when using ``@parameterized.expand``.
+
+
+Migrating from ``nose-parameterized`` to ``parameterized``
+----------------------------------------------------------
+
+To migrate a codebase from ``nose-parameterized`` to ``parameterized``:
+
+1. Update your requirements file, replacing ``nose-parameterized`` with
+ ``parameterized``.
+
+2. Replace all references to ``nose_parameterized`` with ``parameterized``::
+
+ $ perl -pi -e 's/nose_parameterized/parameterized/g' your-codebase/
+
+3. You're done!
+
+
+FAQ
+---
+
+What happened to Python 2.X, 3.5, and 3.6 support?
+ As of version 0.9.0, ``parameterized`` no longer supports Python 2.X, 3.5,
+ or 3.6. Previous versions of ``parameterized`` - 0.8.1 being the latest -
+ will continue to work, but will not receive any new features or bug fixes.
+
+What do you mean when you say "nose is best supported"?
+ There are small caveates 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.
+
+Why not use ``@pytest.mark.parametrize``?
+ Because spelling is difficult. Also, ``parameterized`` doesn't require you
+ to repeat argument names, and (using ``param``) it supports optional
+ keyword arguments.
+
+Why do I get an ``AttributeError: 'function' object has no attribute 'expand'`` with ``@parameterized.expand``?
+ You've likely installed the ``parametrized`` (note the missing *e*)
+ package. Use ``parameterized`` (with the *e*) instead and you'll be all
+ set.
+
+What happened to ``nose-parameterized``?
+ Originally only nose was supported. But now everything is supported, and it
+ only made sense to change the name!
diff --git a/contrib/python/parameterized/py3/.dist-info/top_level.txt b/contrib/python/parameterized/py3/.dist-info/top_level.txt
new file mode 100644
index 00000000000..f543eed175e
--- /dev/null
+++ b/contrib/python/parameterized/py3/.dist-info/top_level.txt
@@ -0,0 +1 @@
+parameterized
diff --git a/contrib/python/parameterized/py3/LICENSE.txt b/contrib/python/parameterized/py3/LICENSE.txt
new file mode 100644
index 00000000000..e58a36e52d8
--- /dev/null
+++ b/contrib/python/parameterized/py3/LICENSE.txt
@@ -0,0 +1,27 @@
+Unless stated otherwise in the source files, all code is copyright 2010 David
+Wolever <[email protected]>. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY DAVID WOLEVER ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL DAVID WOLEVER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of David Wolever.
diff --git a/contrib/python/parameterized/py3/README.rst b/contrib/python/parameterized/py3/README.rst
new file mode 100644
index 00000000000..2afd207a5c7
--- /dev/null
+++ b/contrib/python/parameterized/py3/README.rst
@@ -0,0 +1,654 @@
+Parameterized testing with any Python test framework
+====================================================
+
+.. image:: https://img.shields.io/pypi/v/parameterized
+ :alt: PyPI
+ :target: https://pypi.org/project/parameterized/
+
+.. image:: https://img.shields.io/pypi/dm/parameterized
+ :alt: PyPI - Downloads
+ :target: https://pypi.org/project/parameterized/
+
+.. image:: https://circleci.com/gh/wolever/parameterized.svg?style=svg
+ :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.
+
+.. code:: python
+
+ # test_math.py
+ from nose.tools import assert_equal
+ from parameterized import parameterized, parameterized_class
+
+ import unittest
+ import math
+
+ @parameterized([
+ (2, 2, 4),
+ (2, 3, 8),
+ (1, 9, 1),
+ (0, 9, 0),
+ ])
+ def test_pow(base, exponent, expected):
+ assert_equal(math.pow(base, exponent), expected)
+
+ class TestMathUnitTest(unittest.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)
+
+ @parameterized_class(('a', 'b', 'expected_sum', 'expected_product'), [
+ (1, 2, 3, 2),
+ (5, 5, 10, 25),
+ ])
+ class TestMathClass(unittest.TestCase):
+ def test_add(self):
+ assert_equal(self.a + self.b, self.expected_sum)
+
+ def test_multiply(self):
+ assert_equal(self.a * self.b, self.expected_product)
+
+ @parameterized_class([
+ { "a": 3, "expected": 2 },
+ { "b": 5, "expected": -4 },
+ ])
+ class TestMathClassDict(unittest.TestCase):
+ a = 1
+ b = 1
+
+ def test_subtract(self):
+ assert_equal(self.a - self.b, self.expected)
+
+
+With nose (and nose2)::
+
+ $ nosetests -v test_math.py
+ test_floor_0_negative (test_math.TestMathUnitTest) ... ok
+ test_floor_1_integer (test_math.TestMathUnitTest) ... ok
+ test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
+ test_math.test_pow(2, 2, 4, {}) ... ok
+ test_math.test_pow(2, 3, 8, {}) ... ok
+ test_math.test_pow(1, 9, 1, {}) ... ok
+ test_math.test_pow(0, 9, 0, {}) ... ok
+ test_add (test_math.TestMathClass_0) ... ok
+ test_multiply (test_math.TestMathClass_0) ... ok
+ test_add (test_math.TestMathClass_1) ... ok
+ test_multiply (test_math.TestMathClass_1) ... ok
+ test_subtract (test_math.TestMathClassDict_0) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 12 tests in 0.015s
+
+ OK
+
+As the package name suggests, nose is best supported and will be used for all
+further examples.
+
+
+With py.test (version 2.0 and above)::
+
+ $ py.test -v test_math.py
+ ============================= test session starts ==============================
+ platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
+ collecting ... collected 13 items
+
+ test_math.py::test_pow::[0] PASSED
+ test_math.py::test_pow::[1] PASSED
+ test_math.py::test_pow::[2] PASSED
+ test_math.py::test_pow::[3] PASSED
+ test_math.py::TestMathUnitTest::test_floor_0_negative PASSED
+ test_math.py::TestMathUnitTest::test_floor_1_integer PASSED
+ test_math.py::TestMathUnitTest::test_floor_2_large_fraction PASSED
+ test_math.py::TestMathClass_0::test_add PASSED
+ test_math.py::TestMathClass_0::test_multiply PASSED
+ test_math.py::TestMathClass_1::test_add PASSED
+ test_math.py::TestMathClass_1::test_multiply PASSED
+ test_math.py::TestMathClassDict_0::test_subtract PASSED
+ ==================== 12 passed, 4 warnings in 0.16 seconds =====================
+
+With unittest (and unittest2)::
+
+ $ python -m unittest -v test_math
+ test_floor_0_negative (test_math.TestMathUnitTest) ... ok
+ test_floor_1_integer (test_math.TestMathUnitTest) ... ok
+ test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
+ test_add (test_math.TestMathClass_0) ... ok
+ test_multiply (test_math.TestMathClass_0) ... ok
+ test_add (test_math.TestMathClass_1) ... ok
+ test_multiply (test_math.TestMathClass_1) ... ok
+ test_subtract (test_math.TestMathClassDict_0) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 8 tests in 0.001s
+
+ OK
+
+(note: because unittest does not support test decorators, only tests created
+with ``@parameterized.expand`` will be executed)
+
+With green::
+
+ $ green test_math.py -vvv
+ test_math
+ TestMathClass_1
+ . test_method_a
+ . test_method_b
+ TestMathClass_2
+ . test_method_a
+ . test_method_b
+ TestMathClass_3
+ . test_method_a
+ . test_method_b
+ TestMathUnitTest
+ . test_floor_0_negative
+ . test_floor_1_integer
+ . test_floor_2_large_fraction
+ TestMathClass_0
+ . test_add
+ . test_multiply
+ TestMathClass_1
+ . test_add
+ . test_multiply
+ TestMathClassDict_0
+ . test_subtract
+
+ Ran 12 tests in 0.121s
+
+ OK (passes=9)
+
+
+Installation
+------------
+
+::
+
+ $ pip install parameterized
+
+
+Compatibility
+-------------
+
+`Yes`__ (mostly).
+
+__ https://app.circleci.com/pipelines/github/wolever/parameterized?branch=master
+
+.. list-table::
+ :header-rows: 1
+ :stub-columns: 1
+
+ * -
+ - Py3.7
+ - Py3.8
+ - Py3.9
+ - Py3.10
+ - Py3.11
+ - PyPy3
+ - ``@mock.patch``
+ * - nose
+ - yes
+ - yes
+ - yes
+ - yes
+ - no§
+ - no§
+ - yes
+ * - nose2
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ * - py.test 2
+ - no*
+ - no*
+ - no*
+ - no*
+ - no*
+ - no*
+ - no*
+ * - py.test 3
+ - yes
+ - yes
+ - yes
+ - yes
+ - no*
+ - no*
+ - yes
+ * - py.test 4
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ - no**
+ * - py.test fixtures
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ - no†
+ * - | unittest
+ | (``@parameterized.expand``)
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ - yes
+ * - | unittest2
+ | (``@parameterized.expand``)
+ - yes
+ - yes
+ - yes
+ - yes
+ - no§
+ - no§
+ - yes
+
+§: nose and unittest2 - both of which were last updated in 2015 - sadly do not
+appear to support Python 3.10 or 3.11.
+
+\*: `py.test 2 does not appear to work under Python 3 (#71)`__, and
+`py.test 3 does not appear to work under Python 3.10 or 3.11 (#154)`__.
+
+\*\*: 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
+__ https://github.com/wolever/parameterized/issues/154
+__ https://github.com/wolever/parameterized/issues/34
+__ https://github.com/wolever/parameterized/issues/81
+
+Dependencies
+------------
+
+(this section left intentionally blank)
+
+
+Exhaustive Usage Examples
+--------------------------
+
+The ``@parameterized`` and ``@parameterized.expand`` decorators accept a list
+or iterable of tuples or ``param(...)``, or a callable which returns a list or
+iterable:
+
+.. code:: python
+
+ from parameterized import parameterized, param
+
+ # A list of tuples
+ @parameterized([
+ (2, 3, 5),
+ (3, 5, 8),
+ ])
+ def test_add(a, b, expected):
+ assert_equal(a + b, expected)
+
+ # A list of params
+ @parameterized([
+ param("10", 10),
+ param("10", 16, base=16),
+ ])
+ def test_int(str_val, expected, base=10):
+ assert_equal(int(str_val, base=base), expected)
+
+ # An iterable of params
+ @parameterized(
+ param.explicit(*json.loads(line))
+ for line in open("testcases.jsons")
+ )
+ def test_from_json_file(...):
+ ...
+
+ # A callable which returns a list of tuples
+ def load_test_cases():
+ return [
+ ("test1", ),
+ ("test2", ),
+ ]
+ @parameterized(load_test_cases)
+ def test_from_function(name):
+ ...
+
+.. **
+
+Note that, when using an iterator or a generator, all the items will be loaded
+into memory before the start of the test run (we do this explicitly to ensure
+that generators are exhausted exactly once in multi-process or multi-threaded
+testing environments).
+
+The ``@parameterized`` decorator can be used test class methods, and standalone
+functions:
+
+.. code:: python
+
+ from parameterized import parameterized
+
+ class AddTest(object):
+ @parameterized([
+ (2, 3, 5),
+ ])
+ def test_add(self, a, b, expected):
+ assert_equal(a + b, expected)
+
+ @parameterized([
+ (2, 3, 5),
+ ])
+ def test_add(a, b, expected):
+ assert_equal(a + b, expected)
+
+
+And ``@parameterized.expand`` can be used to generate test methods in
+situations where test generators cannot be used (for example, when the test
+class is a subclass of ``unittest.TestCase``):
+
+.. code:: python
+
+ import unittest
+ from parameterized import parameterized
+
+ class AddTestCase(unittest.TestCase):
+ @parameterized.expand([
+ ("2 and 3", 2, 3, 5),
+ ("3 and 5", 3, 5, 8),
+ ])
+ def test_add(self, _, a, b, expected):
+ assert_equal(a + b, expected)
+
+Will create the test cases::
+
+ $ nosetests example.py
+ test_add_0_2_and_3 (example.AddTestCase) ... ok
+ test_add_1_3_and_5 (example.AddTestCase) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 2 tests in 0.001s
+
+ OK
+
+Note that ``@parameterized.expand`` works by creating new methods on the test
+class. If the first parameter is a string, that string will be added to the end
+of the method name. For example, the test case above will generate the methods
+``test_add_0_2_and_3`` and ``test_add_1_3_and_5``.
+
+The names of the test cases generated by ``@parameterized.expand`` can be
+customized using the ``name_func`` keyword argument. The value should
+be a function which accepts three arguments: ``testcase_func``, ``param_num``,
+and ``params``, and it should return the name of the test case.
+``testcase_func`` will be the function to be tested, ``param_num`` will be the
+index of the test case parameters in the list of parameters, and ``param``
+(an instance of ``param``) will be the parameters which will be used.
+
+.. code:: python
+
+ import unittest
+ from parameterized import parameterized
+
+ def custom_name_func(testcase_func, param_num, param):
+ return "%s_%s" %(
+ testcase_func.__name__,
+ parameterized.to_safe_name("_".join(str(x) for x in param.args)),
+ )
+
+ class AddTestCase(unittest.TestCase):
+ @parameterized.expand([
+ (2, 3, 5),
+ (2, 3, 5),
+ ], name_func=custom_name_func)
+ def test_add(self, a, b, expected):
+ assert_equal(a + b, expected)
+
+Will create the test cases::
+
+ $ nosetests example.py
+ test_add_1_2_3 (example.AddTestCase) ... ok
+ test_add_2_3_5 (example.AddTestCase) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 2 tests in 0.001s
+
+ OK
+
+
+The ``param(...)`` helper class stores the parameters for one specific test
+case. It can be used to pass keyword arguments to test cases:
+
+.. code:: python
+
+ from parameterized import parameterized, param
+
+ @parameterized([
+ param("10", 10),
+ param("10", 16, base=16),
+ ])
+ def test_int(str_val, expected, base=10):
+ assert_equal(int(str_val, base=base), expected)
+
+
+If test cases have a docstring, the parameters for that test case will be
+appended to the first line of the docstring. This behavior can be controlled
+with the ``doc_func`` argument:
+
+.. code:: python
+
+ from parameterized import parameterized
+
+ @parameterized([
+ (1, 2, 3),
+ (4, 5, 9),
+ ])
+ def test_add(a, b, expected):
+ """ Test addition. """
+ assert_equal(a + b, expected)
+
+ def my_doc_func(func, num, param):
+ return "%s: %s with %s" %(num, func.__name__, param)
+
+ @parameterized([
+ (5, 4, 1),
+ (9, 6, 3),
+ ], doc_func=my_doc_func)
+ def test_subtraction(a, b, expected):
+ assert_equal(a - b, expected)
+
+::
+
+ $ nosetests example.py
+ Test addition. [with a=1, b=2, expected=3] ... ok
+ Test addition. [with a=4, b=5, expected=9] ... ok
+ 0: test_subtraction with param(*(5, 4, 1)) ... ok
+ 1: test_subtraction with param(*(9, 6, 3)) ... ok
+
+ ----------------------------------------------------------------------
+ Ran 4 tests in 0.001s
+
+ OK
+
+Finally ``@parameterized_class`` parameterizes an entire class, using
+either a list of attributes, or a list of dicts that will be applied to the
+class:
+
+.. code:: python
+
+ from yourapp.models import User
+ from parameterized import parameterized_class
+
+ @parameterized_class([
+ { "username": "user_1", "access_level": 1 },
+ { "username": "user_2", "access_level": 2, "expected_status_code": 404 },
+ ])
+ class TestUserAccessLevel(TestCase):
+ expected_status_code = 200
+
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
+
+ def test_url_a(self):
+ response = self.client.get('/url')
+ self.assertEqual(response.status_code, self.expected_status_code)
+
+ def tearDown(self):
+ self.client.logout()
+
+
+ @parameterized_class(("username", "access_level", "expected_status_code"), [
+ ("user_1", 1, 200),
+ ("user_2", 2, 404)
+ ])
+ class TestUserAccessLevel(TestCase):
+ def setUp(self):
+ self.client.force_login(User.objects.get(username=self.username)[0])
+
+ def test_url_a(self):
+ response = self.client.get("/url")
+ self.assertEqual(response.status_code, self.expected_status_code)
+
+ def tearDown(self):
+ self.client.logout()
+
+
+The ``@parameterized_class`` decorator accepts a ``class_name_func`` argument,
+which controls the name of the parameterized classes generated by
+``@parameterized_class``:
+
+.. code:: python
+
+ from parameterized import parameterized, parameterized_class
+
+ def get_class_name(cls, num, params_dict):
+ # By default the generated class named includes either the "name"
+ # parameter (if present), or the first string value. This example shows
+ # multiple parameters being included in the generated class name:
+ return "%s_%s_%s%s" %(
+ cls.__name__,
+ num,
+ parameterized.to_safe_name(params_dict['a']),
+ parameterized.to_safe_name(params_dict['b']),
+ )
+
+ @parameterized_class([
+ { "a": "hello", "b": " world!", "expected": "hello world!" },
+ { "a": "say ", "b": " cheese :)", "expected": "say cheese :)" },
+ ], class_name_func=get_class_name)
+ class TestConcatenation(TestCase):
+ def test_concat(self):
+ self.assertEqual(self.a + self.b, self.expected)
+
+::
+
+ $ nosetests -v test_math.py
+ test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok
+ test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok
+
+
+
+Using with Single Parameters
+............................
+
+If a test function only accepts one parameter and the value is not iterable,
+then it is possible to supply a list of values without wrapping each one in a
+tuple:
+
+.. code:: python
+
+ @parameterized([1, 2, 3])
+ def test_greater_than_zero(value):
+ assert value > 0
+
+Note, however, that if the single parameter *is* iterable (such as a list or
+tuple), then it *must* be wrapped in a tuple, list, or the ``param(...)``
+helper:
+
+.. code:: python
+
+ @parameterized([
+ ([1, 2, 3], ),
+ ([3, 3], ),
+ ([6], ),
+ ])
+ def test_sums_to_6(numbers):
+ assert sum(numbers) == 6
+
+(note, also, that Python requires single element tuples to be defined with a
+trailing comma: ``(foo, )``)
+
+
+Using with ``@mock.patch``
+..........................
+
+``parameterized`` can be used with ``mock.patch``, but the argument ordering
+can be confusing. The ``@mock.patch(...)`` decorator must come *below* the
+``@parameterized(...)``, and the mocked parameters must come *last*:
+
+.. 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):
+ ...
+
+Note: the same holds true when using ``@parameterized.expand``.
+
+
+Migrating from ``nose-parameterized`` to ``parameterized``
+----------------------------------------------------------
+
+To migrate a codebase from ``nose-parameterized`` to ``parameterized``:
+
+1. Update your requirements file, replacing ``nose-parameterized`` with
+ ``parameterized``.
+
+2. Replace all references to ``nose_parameterized`` with ``parameterized``::
+
+ $ perl -pi -e 's/nose_parameterized/parameterized/g' your-codebase/
+
+3. You're done!
+
+
+FAQ
+---
+
+What happened to Python 2.X, 3.5, and 3.6 support?
+ As of version 0.9.0, ``parameterized`` no longer supports Python 2.X, 3.5,
+ or 3.6. Previous versions of ``parameterized`` - 0.8.1 being the latest -
+ will continue to work, but will not receive any new features or bug fixes.
+
+What do you mean when you say "nose is best supported"?
+ There are small caveates 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.
+
+Why not use ``@pytest.mark.parametrize``?
+ Because spelling is difficult. Also, ``parameterized`` doesn't require you
+ to repeat argument names, and (using ``param``) it supports optional
+ keyword arguments.
+
+Why do I get an ``AttributeError: 'function' object has no attribute 'expand'`` with ``@parameterized.expand``?
+ You've likely installed the ``parametrized`` (note the missing *e*)
+ package. Use ``parameterized`` (with the *e*) instead and you'll be all
+ set.
+
+What happened to ``nose-parameterized``?
+ Originally only nose was supported. But now everything is supported, and it
+ only made sense to change the name!
diff --git a/contrib/python/parameterized/py3/parameterized/__init__.py b/contrib/python/parameterized/py3/parameterized/__init__.py
new file mode 100644
index 00000000000..9dae02d13eb
--- /dev/null
+++ b/contrib/python/parameterized/py3/parameterized/__init__.py
@@ -0,0 +1,3 @@
+from .parameterized import parameterized, param, parameterized_class
+
+__version__ = "0.9.0"
diff --git a/contrib/python/parameterized/py3/parameterized/parameterized.py b/contrib/python/parameterized/py3/parameterized/parameterized.py
new file mode 100644
index 00000000000..56dc535fd86
--- /dev/null
+++ b/contrib/python/parameterized/py3/parameterized/parameterized.py
@@ -0,0 +1,732 @@
+import re
+import sys
+import inspect
+import warnings
+from typing import Iterable
+from functools import wraps
+from types import MethodType as MethodType
+from collections import namedtuple
+
+try:
+ from unittest import mock
+except ImportError:
+ try:
+ import mock
+ except ImportError:
+ mock = None
+
+try:
+ from collections import OrderedDict as MaybeOrderedDict
+except ImportError:
+ MaybeOrderedDict = dict
+
+from unittest import TestCase
+
+try:
+ from unittest import SkipTest
+except ImportError:
+ class SkipTest(Exception):
+ pass
+
+# NOTE: even though Python 2 support has been dropped, these checks have been
+# left in place to avoid merge conflicts. They can be removed in the future, and
+# future code can be written to assume Python 3.
+PY3 = sys.version_info[0] == 3
+PY2 = sys.version_info[0] == 2
+
+
+if PY3:
+ # Python 3 doesn't have an InstanceType, so just use a dummy type.
+ class InstanceType():
+ pass
+ lzip = lambda *a: list(zip(*a))
+ text_type = str
+ string_types = str,
+ bytes_type = bytes
+ def make_method(func, instance, type):
+ if instance is None:
+ return func
+ return MethodType(func, instance)
+else:
+ from types import InstanceType
+ lzip = zip
+ text_type = unicode
+ bytes_type = str
+ string_types = basestring,
+ def make_method(func, instance, type):
+ return MethodType(func, instance, type)
+
+def to_text(x):
+ if isinstance(x, text_type):
+ return x
+ try:
+ return text_type(x, "utf-8")
+ except UnicodeDecodeError:
+ return text_type(x, "latin1")
+
+CompatArgSpec = namedtuple("CompatArgSpec", "args varargs keywords defaults")
+
+
+def getargspec(func):
+ if PY2:
+ return CompatArgSpec(*inspect.getargspec(func))
+ args = inspect.getfullargspec(func)
+ if args.kwonlyargs:
+ raise TypeError((
+ "parameterized does not (yet) support functions with keyword "
+ "only arguments, but %r has keyword only arguments. "
+ "Please open an issue with your usecase if this affects you: "
+ "https://github.com/wolever/parameterized/issues/new"
+ ) %(func, ))
+ return CompatArgSpec(*args[:4])
+
+
+def skip_on_empty_helper(*a, **kw):
+ raise SkipTest("parameterized input is empty")
+
+
+def reapply_patches_if_need(func):
+
+ def dummy_wrapper(orgfunc):
+ @wraps(orgfunc)
+ def dummy_func(*args, **kwargs):
+ return orgfunc(*args, **kwargs)
+ return dummy_func
+
+ if hasattr(func, 'patchings'):
+ is_original_async = inspect.iscoroutinefunction(func)
+ func = dummy_wrapper(func)
+ tmp_patchings = func.patchings
+ delattr(func, 'patchings')
+ for patch_obj in tmp_patchings:
+ if is_original_async:
+ func = patch_obj.decorate_async_callable(func)
+ else:
+ func = patch_obj.decorate_callable(func)
+ return func
+
+
+# `parameterized.expand` strips out `mock` patches from the source method in favor of re-applying them over the
+# generated methods instead. Sadly, this can cause problems with old versions of the `mock` package, as shown in
+# https://bugs.python.org/issue40126 (bpo-40126).
+#
+# Long story short, bpo-40126 arises whenever the `patchings` list of a `mock`-decorated method is left fully empty.
+#
+# The bug has been fixed in the `mock` code itself since:
+# - Python 3.7.8-rc1, 3.8.3-rc1 and later (for the `unittest.mock` package) [0][1].
+# - Version 4 of the `mock` backport package (https://pypi.org/project/mock/) [2].
+#
+# To work around the problem when running old `mock` versions, we avoid fully stripping out patches from the source
+# method in favor of replacing them with a "dummy" no-op patch instead.
+#
+# [0] https://docs.python.org/release/3.7.10/whatsnew/changelog.html#python-3-7-8-release-candidate-1
+# [1] https://docs.python.org/release/3.8.10/whatsnew/changelog.html#python-3-8-3-release-candidate-1
+# [2] https://mock.readthedocs.io/en/stable/changelog.html#b1
+
+PYTHON_DOESNT_HAVE_FIX_FOR_BPO_40126 = (
+ sys.version_info[:3] < (3, 7, 8) or (sys.version_info[:2] >= (3, 8) and sys.version_info[:3] < (3, 8, 3))
+)
+
+try:
+ import mock as _mock_backport
+except ImportError:
+ _mock_backport = None
+
+MOCK_BACKPORT_DOESNT_HAVE_FIX_FOR_BPO_40126 = _mock_backport is not None and _mock_backport.version_info[0] < 4
+
+AVOID_CLEARING_MOCK_PATCHES = PYTHON_DOESNT_HAVE_FIX_FOR_BPO_40126 or MOCK_BACKPORT_DOESNT_HAVE_FIX_FOR_BPO_40126
+
+
+class DummyPatchTarget(object):
+ dummy_attribute = None
+
+ @staticmethod
+ def create_dummy_patch():
+ if mock is not None:
+ return mock.patch.object(DummyPatchTarget(), "dummy_attribute", new=None)
+ else:
+ raise ImportError("Missing mock package")
+
+
+def delete_patches_if_need(func):
+ if hasattr(func, 'patchings'):
+ if AVOID_CLEARING_MOCK_PATCHES:
+ func.patchings[:] = [DummyPatchTarget.create_dummy_patch()]
+ else:
+ func.patchings[:] = []
+
+
+_param = namedtuple("param", "args kwargs")
+
+class param(_param):
+ """ Represents a single parameter to a test case.
+
+ For example::
+
+ >>> p = param("foo", bar=16)
+ >>> p
+ param("foo", bar=16)
+ >>> p.args
+ ('foo', )
+ >>> p.kwargs
+ {'bar': 16}
+
+ Intended to be used as an argument to ``@parameterized``::
+
+ @parameterized([
+ param("foo", bar=16),
+ ])
+ def test_stuff(foo, bar=16):
+ pass
+ """
+
+ def __new__(cls, *args , **kwargs):
+ return _param.__new__(cls, args, kwargs)
+
+ @classmethod
+ def explicit(cls, args=None, kwargs=None):
+ """ Creates a ``param`` by explicitly specifying ``args`` and
+ ``kwargs``::
+
+ >>> param.explicit([1,2,3])
+ param(*(1, 2, 3))
+ >>> param.explicit(kwargs={"foo": 42})
+ param(*(), **{"foo": "42"})
+ """
+ args = args or ()
+ kwargs = kwargs or {}
+ return cls(*args, **kwargs)
+
+ @classmethod
+ def from_decorator(cls, args):
+ """ Returns an instance of ``param()`` for ``@parameterized`` argument
+ ``args``::
+
+ >>> param.from_decorator((42, ))
+ param(args=(42, ), kwargs={})
+ >>> param.from_decorator("foo")
+ param(args=("foo", ), kwargs={})
+ """
+ if isinstance(args, param):
+ return args
+ elif isinstance(args, (str, bytes)) or not isinstance(args, Iterable):
+ args = (args, )
+ try:
+ return cls(*args)
+ except TypeError as e:
+ if "after * must be" not in str(e):
+ raise
+ raise TypeError(
+ "Parameters must be tuples, but %r is not (hint: use '(%r, )')"
+ %(args, args),
+ )
+
+ def __repr__(self):
+ return "param(*%r, **%r)" %self
+
+
+class QuietOrderedDict(MaybeOrderedDict):
+ """ When OrderedDict is available, use it to make sure that the kwargs in
+ doc strings are consistently ordered. """
+ __str__ = dict.__str__
+ __repr__ = dict.__repr__
+
+
+def parameterized_argument_value_pairs(func, p):
+ """Return tuples of parameterized arguments and their values.
+
+ This is useful if you are writing your own doc_func
+ function and need to know the values for each parameter name::
+
+ >>> def func(a, foo=None, bar=42, **kwargs): pass
+ >>> p = param(1, foo=7, extra=99)
+ >>> parameterized_argument_value_pairs(func, p)
+ [("a", 1), ("foo", 7), ("bar", 42), ("**kwargs", {"extra": 99})]
+
+ If the function's first argument is named ``self`` then it will be
+ ignored::
+
+ >>> def func(self, a): pass
+ >>> p = param(1)
+ >>> parameterized_argument_value_pairs(func, p)
+ [("a", 1)]
+
+ Additionally, empty ``*args`` or ``**kwargs`` will be ignored::
+
+ >>> def func(foo, *args): pass
+ >>> p = param(1)
+ >>> parameterized_argument_value_pairs(func, p)
+ [("foo", 1)]
+ >>> p = param(1, 16)
+ >>> parameterized_argument_value_pairs(func, p)
+ [("foo", 1), ("*args", (16, ))]
+ """
+ argspec = getargspec(func)
+ arg_offset = 1 if argspec.args[:1] == ["self"] else 0
+
+ named_args = argspec.args[arg_offset:]
+
+ result = lzip(named_args, p.args)
+ named_args = argspec.args[len(result) + arg_offset:]
+ varargs = p.args[len(result):]
+
+ result.extend([
+ (name, p.kwargs.get(name, default))
+ for (name, default)
+ in zip(named_args, argspec.defaults or [])
+ ])
+
+ seen_arg_names = set([ n for (n, _) in result ])
+ keywords = QuietOrderedDict(sorted([
+ (name, p.kwargs[name])
+ for name in p.kwargs
+ if name not in seen_arg_names
+ ]))
+
+ if varargs:
+ result.append(("*%s" %(argspec.varargs, ), tuple(varargs)))
+
+ if keywords:
+ result.append(("**%s" %(argspec.keywords, ), keywords))
+
+ return result
+
+
+def short_repr(x, n=64):
+ """ A shortened repr of ``x`` which is guaranteed to be ``unicode``::
+
+ >>> short_repr("foo")
+ u"foo"
+ >>> short_repr("123456789", n=4)
+ u"12...89"
+ """
+
+ x_repr = to_text(repr(x))
+ if len(x_repr) > n:
+ x_repr = x_repr[:n//2] + "..." + x_repr[len(x_repr) - n//2:]
+ return x_repr
+
+
+def default_doc_func(func, num, p):
+ if func.__doc__ is None:
+ return None
+
+ all_args_with_values = parameterized_argument_value_pairs(func, p)
+
+ # Assumes that the function passed is a bound method.
+ descs = ["%s=%s" %(n, short_repr(v)) for n, v in all_args_with_values]
+
+ # The documentation might be a multiline string, so split it
+ # and just work with the first string, ignoring the period
+ # at the end if there is one.
+ first, nl, rest = func.__doc__.lstrip().partition("\n")
+ suffix = ""
+ if first.endswith("."):
+ suffix = "."
+ first = first[:-1]
+ args = "%s[with %s]" %(len(first) and " " or "", ", ".join(descs))
+ return "".join(
+ to_text(x)
+ for x in [first.rstrip(), args, suffix, nl, rest]
+ )
+
+
+def default_name_func(func, num, p):
+ base_name = func.__name__
+ name_suffix = "_%s" %(num, )
+
+ if len(p.args) > 0 and isinstance(p.args[0], string_types):
+ name_suffix += "_" + parameterized.to_safe_name(p.args[0])
+ return base_name + name_suffix
+
+
+_test_runner_override = None
+_test_runner_guess = False
+_test_runners = set(["unittest", "unittest2", "nose", "nose2", "pytest"])
+_test_runner_aliases = {
+ "_pytest": "pytest",
+}
+
+
+def set_test_runner(name):
+ global _test_runner_override
+ if name not in _test_runners:
+ raise TypeError(
+ "Invalid test runner: %r (must be one of: %s)"
+ %(name, ", ".join(_test_runners)),
+ )
+ _test_runner_override = name
+
+
+def detect_runner():
+ """ Guess which test runner we're using by traversing the stack and looking
+ for the first matching module. This *should* be reasonably safe, as
+ it's done during test discovery where the test runner should be the
+ stack frame immediately outside. """
+ if _test_runner_override is not None:
+ return _test_runner_override
+ global _test_runner_guess
+ if _test_runner_guess is False:
+ stack = inspect.stack()
+ for record in reversed(stack):
+ frame = record[0]
+ module = frame.f_globals.get("__name__").partition(".")[0]
+ if module in _test_runner_aliases:
+ module = _test_runner_aliases[module]
+ if module in _test_runners:
+ _test_runner_guess = module
+ break
+ if record[1].endswith("python2.6/unittest.py"):
+ _test_runner_guess = "unittest"
+ break
+ else:
+ _test_runner_guess = None
+ return _test_runner_guess
+
+
+
+class parameterized(object):
+ """ Parameterize a test case::
+
+ class TestInt(object):
+ @parameterized([
+ ("A", 10),
+ ("F", 15),
+ param("10", 42, base=42)
+ ])
+ def test_int(self, input, expected, base=16):
+ actual = int(input, base=base)
+ assert_equal(actual, expected)
+
+ @parameterized([
+ (2, 3, 5)
+ (3, 5, 8),
+ ])
+ def test_add(a, b, expected):
+ assert_equal(a + b, expected)
+ """
+
+ def __init__(self, input, doc_func=None, skip_on_empty=False):
+ self.get_input = self.input_as_callable(input)
+ self.doc_func = doc_func or default_doc_func
+ self.skip_on_empty = skip_on_empty
+
+ def __call__(self, test_func):
+ self.assert_not_in_testcase_subclass()
+
+ @wraps(test_func)
+ def wrapper(test_self=None):
+ test_cls = test_self and type(test_self)
+ if test_self is not None:
+ if issubclass(test_cls, InstanceType):
+ raise TypeError((
+ "@parameterized can't be used with old-style classes, but "
+ "%r has an old-style class. Consider using a new-style "
+ "class, or '@parameterized.expand' "
+ "(see http://stackoverflow.com/q/54867/71522 for more "
+ "information on old-style classes)."
+ ) %(test_self, ))
+
+ original_doc = wrapper.__doc__
+ for num, args in enumerate(wrapper.parameterized_input):
+ p = param.from_decorator(args)
+ unbound_func, nose_tuple = self.param_as_nose_tuple(test_self, test_func, num, p)
+ try:
+ wrapper.__doc__ = nose_tuple[0].__doc__
+ # Nose uses `getattr(instance, test_func.__name__)` to get
+ # a method bound to the test instance (as opposed to a
+ # method bound to the instance of the class created when
+ # tests were being enumerated). Set a value here to make
+ # sure nose can get the correct test method.
+ if test_self is not None:
+ setattr(test_cls, test_func.__name__, unbound_func)
+ yield nose_tuple
+ finally:
+ 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):
+ nose_func = wraps(func)(lambda *args: func(*args[:-1], **args[-1]))
+ nose_func.__doc__ = self.doc_func(func, num, p)
+ # Track the unbound function because we need to setattr the unbound
+ # function onto the class for nose to work (see comments above), and
+ # Python 3 doesn't let us pull the function out of a bound method.
+ unbound_func = nose_func
+ if test_self is not None:
+ # Under nose on Py2 we need to return an unbound method to make
+ # sure that the `self` in the method is properly shared with the
+ # `self` used in `setUp` and `tearDown`. But only there. Everyone
+ # else needs a bound method.
+ func_self = (
+ None if PY2 and detect_runner() == "nose" else
+ test_self
+ )
+ nose_func = make_method(nose_func, func_self, type(test_self))
+ return unbound_func, (nose_func, ) + p.args + (p.kwargs or {}, )
+
+ def assert_not_in_testcase_subclass(self):
+ parent_classes = self._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.")
+
+ def _terrible_magic_get_defining_classes(self):
+ """ 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[4]
+ code_context = frame[4] and frame[4][0].strip()
+ if not (code_context and code_context.startswith("class ")):
+ return []
+ _, _, parents = code_context.partition("(")
+ parents, _, _ = parents.partition(")")
+ return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
+
+ @classmethod
+ def input_as_callable(cls, input):
+ if callable(input):
+ return lambda: cls.check_input_values(input())
+ input_values = cls.check_input_values(input)
+ return lambda: input_values
+
+ @classmethod
+ def check_input_values(cls, input_values):
+ # Explicitly convery non-list inputs to a list so that:
+ # 1. A helpful exception will be raised if they aren't iterable, and
+ # 2. Generators are unwrapped exactly once (otherwise `nosetests
+ # --processes=n` has issues; see:
+ # https://github.com/wolever/nose-parameterized/pull/31)
+ if not isinstance(input_values, list):
+ input_values = list(input_values)
+ return [ param.from_decorator(p) for p in input_values ]
+
+ @classmethod
+ def expand(cls, input, name_func=None, doc_func=None, skip_on_empty=False,
+ namespace=None, **legacy):
+ """ 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.
+
+ :param input: An iterable of values to pass to the test function.
+ :param name_func: A function that takes a single argument (the
+ value from the input iterable) and returns a string to use as
+ the name of the test case. If not provided, the name of the
+ test case will be the name of the test function with the
+ parameter value appended.
+ :param doc_func: A function that takes a single argument (the
+ value from the input iterable) and returns a string to use as
+ the docstring of the test case. If not provided, the docstring
+ of the test case will be the docstring of the test function.
+ :param skip_on_empty: If True, the test will be skipped if the
+ input iterable is empty. If False, a ValueError will be raised
+ if the input iterable is empty.
+ :param namespace: The namespace (dict-like) to inject the test cases
+ into. If not provided, the namespace of the test function will
+ be used.
+
+ >>> @parameterized.expand([("foo", 1, 2)])
+ ... def test_add1(name, input, expected):
+ ... actual = add1(input)
+ ... assert_equal(actual, expected)
+ ...
+ >>> locals()
+ ... 'test_add1_foo_0': <function ...> ...
+ >>>
+ """
+
+ if "testcase_func_name" in legacy:
+ warnings.warn("testcase_func_name= is deprecated; use name_func=",
+ DeprecationWarning, stacklevel=2)
+ if not name_func:
+ name_func = legacy["testcase_func_name"]
+
+ if "testcase_func_doc" in legacy:
+ warnings.warn("testcase_func_doc= is deprecated; use doc_func=",
+ DeprecationWarning, stacklevel=2)
+ if not doc_func:
+ doc_func = legacy["testcase_func_doc"]
+
+ doc_func = doc_func or default_doc_func
+ name_func = name_func or default_name_func
+
+ def parameterized_expand_wrapper(f, instance=None):
+ frame_locals = namespace
+ if frame_locals is None:
+ frame_locals = inspect.currentframe().f_back.f_locals
+
+ parameters = cls.input_as_callable(input)()
+
+ if not parameters:
+ if not skip_on_empty:
+ raise ValueError(
+ "Parameters iterable is empty (hint: use "
+ "`parameterized.expand([], skip_on_empty=True)` to skip "
+ "this test when the input is empty)"
+ )
+ return wraps(f)(skip_on_empty_helper)
+
+ digits = len(str(len(parameters) - 1))
+ for num, p in enumerate(parameters):
+ name = name_func(f, "{num:0>{digits}}".format(digits=digits, num=num), p)
+ # If the original function has patches applied by 'mock.patch',
+ # re-construct all patches on the just former decoration layer
+ # of param_as_standalone_func so as not to share
+ # patch objects between new functions
+ nf = reapply_patches_if_need(f)
+ frame_locals[name] = cls.param_as_standalone_func(p, nf, name)
+ frame_locals[name].__doc__ = doc_func(f, num, p)
+
+ # Delete original patches to prevent new function from evaluating
+ # original patching object as well as re-constructed patches.
+ delete_patches_if_need(f)
+
+ f.__test__ = False
+
+ return parameterized_expand_wrapper
+
+ @classmethod
+ def param_as_standalone_func(cls, p, func, name):
+ if inspect.iscoroutinefunction(func):
+ @wraps(func)
+ async def standalone_func(*a, **kw):
+ return await func(*(a + p.args), **p.kwargs, **kw)
+ else:
+ @wraps(func)
+ def standalone_func(*a, **kw):
+ return func(*(a + p.args), **p.kwargs, **kw)
+
+ standalone_func.__name__ = name
+
+ # place_as is used by py.test to determine what source file should be
+ # used for this test.
+ standalone_func.place_as = func
+
+ # Remove __wrapped__ because py.test will try to look at __wrapped__
+ # to determine which parameters should be used with this test case,
+ # and obviously we don't need it to do any parameterization.
+ try:
+ del standalone_func.__wrapped__
+ except AttributeError:
+ pass
+ return standalone_func
+
+ @classmethod
+ def to_safe_name(cls, s):
+ if not isinstance(s, str):
+ s = str(s)
+ return str(re.sub("[^a-zA-Z0-9_]+", "_", s))
+
+
+def parameterized_class(attrs, input_values=None, class_name_func=None, classname_func=None):
+ """ Parameterizes a test class by setting attributes on the class.
+
+ Can be used in two ways:
+
+ 1) With a list of dictionaries containing attributes to override::
+
+ @parameterized_class([
+ { "username": "foo" },
+ { "username": "bar", "access_level": 2 },
+ ])
+ class TestUserAccessLevel(TestCase):
+ ...
+
+ 2) With a tuple of attributes, then a list of tuples of values:
+
+ @parameterized_class(("username", "access_level"), [
+ ("foo", 1),
+ ("bar", 2)
+ ])
+ class TestUserAccessLevel(TestCase):
+ ...
+
+ """
+
+ if isinstance(attrs, string_types):
+ attrs = [attrs]
+
+ input_dicts = (
+ attrs if input_values is None else
+ [dict(zip(attrs, vals)) for vals in input_values]
+ )
+
+ class_name_func = class_name_func or default_class_name_func
+
+ if classname_func:
+ warnings.warn(
+ "classname_func= is deprecated; use class_name_func= instead. "
+ "See: https://github.com/wolever/parameterized/pull/74#issuecomment-613577057",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ class_name_func = lambda cls, idx, input: classname_func(cls, idx, input_dicts)
+
+ def decorator(base_class):
+ test_class_module = sys.modules[base_class.__module__].__dict__
+ for idx, input_dict in enumerate(input_dicts):
+ test_class_dict = dict(base_class.__dict__)
+ test_class_dict.update(input_dict)
+
+ name = class_name_func(base_class, idx, input_dict)
+
+ test_class_module[name] = type(name, (base_class, ), test_class_dict)
+
+ # We need to leave the base class in place (see issue #73), but if we
+ # leave the test_ methods in place, the test runner will try to pick
+ # them up and run them... which doesn't make sense, since no parameters
+ # will have been applied.
+ # Address this by iterating over the base class and remove all test
+ # methods.
+ for method_name in list(base_class.__dict__):
+ if method_name.startswith("test"):
+ delattr(base_class, method_name)
+ return base_class
+
+ return decorator
+
+
+def get_class_name_suffix(params_dict):
+ if "name" in params_dict:
+ return parameterized.to_safe_name(params_dict["name"])
+
+ params_vals = (
+ params_dict.values() if PY3 else
+ (v for (_, v) in sorted(params_dict.items()))
+ )
+ return parameterized.to_safe_name(next((
+ v for v in params_vals
+ if isinstance(v, string_types)
+ ), ""))
+
+
+def default_class_name_func(cls, num, params_dict):
+ suffix = get_class_name_suffix(params_dict)
+ return "%s_%s%s" %(
+ cls.__name__,
+ num,
+ suffix and "_" + suffix,
+ )
diff --git a/contrib/python/parameterized/py3/ya.make b/contrib/python/parameterized/py3/ya.make
new file mode 100644
index 00000000000..70d243f622d
--- /dev/null
+++ b/contrib/python/parameterized/py3/ya.make
@@ -0,0 +1,23 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(0.9.0)
+
+LICENSE(BSD-3-Clause)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ parameterized/__init__.py
+ parameterized/parameterized.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/parameterized/py3/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
diff --git a/contrib/python/parameterized/ya.make b/contrib/python/parameterized/ya.make
new file mode 100644
index 00000000000..8a6d8706c0a
--- /dev/null
+++ b/contrib/python/parameterized/ya.make
@@ -0,0 +1,18 @@
+PY23_LIBRARY()
+
+LICENSE(Service-Py23-Proxy)
+
+IF (PYTHON2)
+ PEERDIR(contrib/python/parameterized/py2)
+ELSE()
+ PEERDIR(contrib/python/parameterized/py3)
+ENDIF()
+
+NO_LINT()
+
+END()
+
+RECURSE(
+ py2
+ py3
+)