diff options
| author | maxim-yurchuk <[email protected]> | 2024-10-09 12:29:46 +0300 |
|---|---|---|
| committer | maxim-yurchuk <[email protected]> | 2024-10-09 13:14:22 +0300 |
| commit | 9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80 (patch) | |
| tree | a8fb3181d5947c0d78cf402aa56e686130179049 /contrib/python/parameterized | |
| parent | a44b779cd359f06c3ebbef4ec98c6b38609d9d85 (diff) | |
publishFullContrib: true for ydb
<HIDDEN_URL>
commit_hash:c82a80ac4594723cebf2c7387dec9c60217f603e
Diffstat (limited to 'contrib/python/parameterized')
7 files changed, 1358 insertions, 0 deletions
diff --git a/contrib/python/parameterized/py2/.dist-info/METADATA b/contrib/python/parameterized/py2/.dist-info/METADATA new file mode 100644 index 00000000000..e07835d0ea0 --- /dev/null +++ b/contrib/python/parameterized/py2/.dist-info/METADATA @@ -0,0 +1,683 @@ +Metadata-Version: 2.1 +Name: parameterized +Version: 0.8.1 +Summary: Parameterized testing with any Python test framework +Home-page: https://github.com/wolever/parameterized +Author: David Wolever +Author-email: [email protected] +License: FreeBSD +Platform: UNKNOWN +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: BSD License +Provides-Extra: dev +Requires-Dist: jinja2 ; extra == 'dev' + +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/py2/.dist-info/top_level.txt b/contrib/python/parameterized/py2/.dist-info/top_level.txt new file mode 100644 index 00000000000..f543eed175e --- /dev/null +++ b/contrib/python/parameterized/py2/.dist-info/top_level.txt @@ -0,0 +1 @@ +parameterized diff --git a/contrib/python/parameterized/py2/.yandex_meta/yamaker.yaml b/contrib/python/parameterized/py2/.yandex_meta/yamaker.yaml new file mode 100644 index 00000000000..dd9aba34bc4 --- /dev/null +++ b/contrib/python/parameterized/py2/.yandex_meta/yamaker.yaml @@ -0,0 +1,2 @@ +exclude: + - nose_parameterized/* diff --git a/contrib/python/parameterized/py2/parameterized/__init__.py b/contrib/python/parameterized/py2/parameterized/__init__.py new file mode 100644 index 00000000000..25ff0f19084 --- /dev/null +++ b/contrib/python/parameterized/py2/parameterized/__init__.py @@ -0,0 +1,3 @@ +from .parameterized import parameterized, param, parameterized_class + +__version__ = "0.8.1" diff --git a/contrib/python/parameterized/py2/parameterized/parameterized.py b/contrib/python/parameterized/py2/parameterized/parameterized.py new file mode 100644 index 00000000000..969a1574598 --- /dev/null +++ b/contrib/python/parameterized/py2/parameterized/parameterized.py @@ -0,0 +1,642 @@ +import re +import sys +import inspect +import warnings +from functools import wraps +from types import MethodType as MethodType +from collections import namedtuple + +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 + +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'): + func = dummy_wrapper(func) + tmp_patchings = func.patchings + delattr(func, 'patchings') + for patch_obj in tmp_patchings: + func = patch_obj.decorate_callable(func) + return func + + +def delete_patches_if_need(func): + if hasattr(func, 'patchings'): + 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, string_types): + 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 disocvery 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, + **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. + + >>> @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 = 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): + @wraps(func) + def standalone_func(*a): + return func(*(a + p.args), **p.kwargs) + 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): + 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/py2/ya.make b/contrib/python/parameterized/py2/ya.make new file mode 100644 index 00000000000..8e92750f331 --- /dev/null +++ b/contrib/python/parameterized/py2/ya.make @@ -0,0 +1,25 @@ +# Generated by devtools/yamaker (pypi). + +PY2_LIBRARY() + +SUBSCRIBER(g:feedback_platform g:python-contrib) + +VERSION(0.8.1) + +LICENSE(BSD-3-Clause) + +NO_LINT() + +PY_SRCS( + TOP_LEVEL + parameterized/__init__.py + parameterized/parameterized.py +) + +RESOURCE_FILES( + PREFIX contrib/python/parameterized/py2/ + .dist-info/METADATA + .dist-info/top_level.txt +) + +END() diff --git a/contrib/python/parameterized/py3/.yandex_meta/yamaker.yaml b/contrib/python/parameterized/py3/.yandex_meta/yamaker.yaml new file mode 100644 index 00000000000..dd9aba34bc4 --- /dev/null +++ b/contrib/python/parameterized/py3/.yandex_meta/yamaker.yaml @@ -0,0 +1,2 @@ +exclude: + - nose_parameterized/* |
