Skip to content

Commit

Permalink
Remove code to support Python versions before 3.7
Browse files Browse the repository at this point in the history
  • Loading branch information
eltoder committed Nov 10, 2023
1 parent b9f6a64 commit 2d5ef47
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 170 deletions.
156 changes: 22 additions & 134 deletions parameterized/parameterized.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,83 +2,10 @@
import sys
import inspect
import warnings
from typing import Iterable
from collections.abc 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])
from unittest import SkipTest, TestCase, mock


def skip_on_empty_helper(*a, **kw):
Expand Down Expand Up @@ -142,10 +69,7 @@ class DummyPatchTarget(object):

@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")
return mock.patch.object(DummyPatchTarget(), "dummy_attribute", new=None)


def delete_patches_if_need(func):
Expand All @@ -158,6 +82,7 @@ def delete_patches_if_need(func):

_param = namedtuple("param", "args kwargs")


class param(_param):
""" Represents a single parameter to a test case.
Expand All @@ -180,7 +105,7 @@ def test_stuff(foo, bar=16):
pass
"""

def __new__(cls, *args , **kwargs):
def __new__(cls, *args, **kwargs):
return _param.__new__(cls, args, kwargs)

@classmethod
Expand Down Expand Up @@ -225,13 +150,6 @@ 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.
Expand Down Expand Up @@ -261,12 +179,12 @@ def parameterized_argument_value_pairs(func, p):
>>> parameterized_argument_value_pairs(func, p)
[("foo", 1), ("*args", (16, ))]
"""
argspec = getargspec(func)
argspec = inspect.getfullargspec(func)
arg_offset = 1 if argspec.args[:1] == ["self"] else 0

named_args = argspec.args[arg_offset:]

result = lzip(named_args, p.args)
result = list(zip(named_args, p.args))
named_args = argspec.args[len(result) + arg_offset:]
varargs = p.args[len(result):]

Expand All @@ -276,8 +194,8 @@ def parameterized_argument_value_pairs(func, p):
in zip(named_args, argspec.defaults or [])
])

seen_arg_names = set([ n for (n, _) in result ])
keywords = QuietOrderedDict(sorted([
seen_arg_names = {n for (n, _) in result}
keywords = dict(sorted([
(name, p.kwargs[name])
for name in p.kwargs
if name not in seen_arg_names
Expand All @@ -287,7 +205,7 @@ def parameterized_argument_value_pairs(func, p):
result.append(("*%s" %(argspec.varargs, ), tuple(varargs)))

if keywords:
result.append(("**%s" %(argspec.keywords, ), keywords))
result.append(("**%s" %(argspec.varkw, ), keywords))

return result

Expand All @@ -301,7 +219,7 @@ def short_repr(x, n=64):
u"12...89"
"""

x_repr = to_text(repr(x))
x_repr = repr(x)
if len(x_repr) > n:
x_repr = x_repr[:n//2] + "..." + x_repr[len(x_repr) - n//2:]
return x_repr
Expand All @@ -325,24 +243,21 @@ def default_doc_func(func, num, p):
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]
)
return "".join([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):
if len(p.args) > 0 and isinstance(p.args[0], str):
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_runners = {"unittest", "unittest2", "nose", "nose2", "pytest"}
_test_runner_aliases = {
"_pytest": "pytest",
}
Expand Down Expand Up @@ -384,7 +299,6 @@ def detect_runner():
return _test_runner_guess



class parameterized(object):
""" Parameterize a test case::
Expand Down Expand Up @@ -417,20 +331,10 @@ def __call__(self, test_func):
@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)
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
Expand All @@ -439,7 +343,7 @@ def wrapper(test_self=None):
# 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)
setattr(test_cls, test_func.__name__, nose_tuple[0].__func__)
yield nose_tuple
finally:
if test_self is not None:
Expand All @@ -465,21 +369,9 @@ def wrapper(test_self=None):
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 {}, )
nose_func = nose_func.__get__(test_self)
return (nose_func, *p.args, p.kwargs or {})

def assert_not_in_testcase_subclass(self):
parent_classes = self._terrible_magic_get_defining_classes()
Expand Down Expand Up @@ -521,7 +413,7 @@ def check_input_values(cls, input_values):
# 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 ]
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,
Expand Down Expand Up @@ -666,7 +558,7 @@ class TestUserAccessLevel(TestCase):
"""

if isinstance(attrs, string_types):
if isinstance(attrs, str):
attrs = [attrs]

input_dicts = (
Expand Down Expand Up @@ -713,13 +605,9 @@ 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)
v for v in params_dict.values()
if isinstance(v, str)
), ""))


Expand Down
39 changes: 3 additions & 36 deletions parameterized/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import inspect
import sys
import mock
from functools import wraps
from unittest import TestCase
from unittest import TestCase, mock
try:
from nose.tools import assert_equal, assert_raises
except ImportError:
Expand All @@ -14,7 +13,7 @@ def assert_raises(*args, **kwds):
return TestCase().assertRaises(*args, **kwds)

from .parameterized import (
PY3, PY2, parameterized, param, parameterized_argument_value_pairs,
parameterized, param, parameterized_argument_value_pairs,
short_repr, detect_runner, parameterized_class, SkipTest,
)

Expand All @@ -35,7 +34,7 @@ def assert_raises_regexp_decorator(expected_exception, expected_regexp):
def func_decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
with self.assertRaisesRegexp(expected_exception, expected_regexp):
with self.assertRaisesRegex(expected_exception, expected_regexp):
func(self, *args, **kwargs)

return wrapper
Expand All @@ -51,9 +50,6 @@ def wrapper(self, *args, **kwargs):
SKIP_FLAGS = {
"generator": UNITTEST,
"standalone": UNITTEST,
# nose2 doesn't run tests on old-style classes under Py2, so don't expect
# these tests to run under nose2.
"py2nose2": (PY2 and NOSE2),
"pytest": PYTEST,
}

Expand Down Expand Up @@ -329,7 +325,6 @@ def test_mock_patch_multiple_standalone(param, umask, getpid):
)



class TestParamerizedOnTestCase(TestCase):
expect([
"test_on_TestCase(42, bar=None)",
Expand Down Expand Up @@ -374,7 +369,6 @@ def _assert_docstring(self, expected_docstring, rstrip=False):
stack = inspect.stack()
f_locals = stack[3][0].f_locals
test_method = (
f_locals.get("testMethod") or # Py27
f_locals.get("function") or # Py33
f_locals.get("method") or # Py38
f_locals.get("testfunction") or # Py382
Expand Down Expand Up @@ -488,33 +482,6 @@ def tearDownModule():
missing = sorted(list(missing_tests))
assert_equal(missing, [])

def test_old_style_classes():
if PY3:
raise SkipTest("Py3 doesn't have old-style classes")
class OldStyleClass:
@parameterized(["foo"])
def parameterized_method(self, param):
pass
try:
list(OldStyleClass().parameterized_method())
except TypeError as e:
assert_contains(str(e), "new-style")
assert_contains(str(e), "parameterized.expand")
assert_contains(str(e), "OldStyleClass")
else:
raise AssertionError("expected TypeError not raised by old-style class")


class TestOldStyleClass:
expect("py2nose2 generator", [
"test_on_old_style_class('foo')",
"test_on_old_style_class('bar')",
])

@parameterized.expand(["foo", "bar"])
def test_old_style_classes(self, param):
missing_tests.remove("test_on_old_style_class(%r)" %(param, ))


@parameterized([
("", param(), []),
Expand Down

0 comments on commit 2d5ef47

Please sign in to comment.