From ca6b1ad2ce16788d5653a0efbc0628e3c66744f6 Mon Sep 17 00:00:00 2001 From: gfyoung Date: Fri, 7 Dec 2018 02:31:31 -0800 Subject: [PATCH] REF/TST: Add more pytest idiom to util/test_util Also breaks up test_util into multiple test modules by the function or method tested. --- pandas/tests/util/test_deprecate_kwarg.py | 93 +++ pandas/tests/util/test_locale.py | 94 ++++ pandas/tests/util/test_move.py | 79 +++ pandas/tests/util/test_safe_import.py | 45 ++ pandas/tests/util/test_util.py | 528 +----------------- pandas/tests/util/test_validate_args.py | 76 +++ .../util/test_validate_args_and_kwargs.py | 105 ++++ pandas/tests/util/test_validate_kwargs.py | 72 +++ 8 files changed, 587 insertions(+), 505 deletions(-) create mode 100644 pandas/tests/util/test_deprecate_kwarg.py create mode 100644 pandas/tests/util/test_locale.py create mode 100644 pandas/tests/util/test_move.py create mode 100644 pandas/tests/util/test_safe_import.py create mode 100644 pandas/tests/util/test_validate_args.py create mode 100644 pandas/tests/util/test_validate_args_and_kwargs.py create mode 100644 pandas/tests/util/test_validate_kwargs.py diff --git a/pandas/tests/util/test_deprecate_kwarg.py b/pandas/tests/util/test_deprecate_kwarg.py new file mode 100644 index 0000000000000..7287df9db8a62 --- /dev/null +++ b/pandas/tests/util/test_deprecate_kwarg.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +import pytest + +from pandas.util._decorators import deprecate_kwarg + +import pandas.util.testing as tm + + +@deprecate_kwarg("old", "new") +def _f1(new=False): + return new + + +_f2_mappings = {"yes": True, "no": False} + + +@deprecate_kwarg("old", "new", _f2_mappings) +def _f2(new=False): + return new + + +def _f3_mapping(x): + return x + 1 + + +@deprecate_kwarg("old", "new", _f3_mapping) +def _f3(new=0): + return new + + +@pytest.mark.parametrize("key,klass", [ + ("old", FutureWarning), + ("new", None) +]) +def test_deprecate_kwarg(key, klass): + x = 78 + + with tm.assert_produces_warning(klass): + assert _f1(**{key: x}) == x + + +@pytest.mark.parametrize("key", list(_f2_mappings.keys())) +def test_dict_deprecate_kwarg(key): + with tm.assert_produces_warning(FutureWarning): + assert _f2(old=key) == _f2_mappings[key] + + +@pytest.mark.parametrize("key", ["bogus", 12345, -1.23]) +def test_missing_deprecate_kwarg(key): + with tm.assert_produces_warning(FutureWarning): + assert _f2(old=key) == key + + +@pytest.mark.parametrize("x", [1, -1.4, 0]) +def test_callable_deprecate_kwarg(x): + with tm.assert_produces_warning(FutureWarning): + assert _f3(old=x) == _f3_mapping(x) + + +def test_callable_deprecate_kwarg_fail(): + msg = "((can only|cannot) concatenate)|(must be str)|(Can't convert)" + + with pytest.raises(TypeError, match=msg): + _f3(old="hello") + + +def test_bad_deprecate_kwarg(): + msg = "mapping from old to new argument values must be dict or callable!" + + with pytest.raises(TypeError, match=msg): + @deprecate_kwarg("old", "new", 0) + def f4(new=None): + return new + + +@deprecate_kwarg("old", None) +def _f4(old=True, unchanged=True): + return old, unchanged + + +@pytest.mark.parametrize("key", ["old", "unchanged"]) +def test_deprecate_keyword(key): + x = 9 + + if key == "old": + klass = FutureWarning + expected = (x, True) + else: + klass = None + expected = (True, x) + + with tm.assert_produces_warning(klass): + assert _f4(**{key: x}) == expected diff --git a/pandas/tests/util/test_locale.py b/pandas/tests/util/test_locale.py new file mode 100644 index 0000000000000..b848b22994e7a --- /dev/null +++ b/pandas/tests/util/test_locale.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +import codecs +import locale +import os + +import pytest + +from pandas.compat import is_platform_windows + +import pandas.core.common as com +import pandas.util.testing as tm + +_all_locales = tm.get_locales() or [] +_current_locale = locale.getlocale() + +# Don't run any of these tests if we are on Windows or have no locales. +pytestmark = pytest.mark.skipif(is_platform_windows() or not _all_locales, + reason="Need non-Windows and locales") + +_skip_if_only_one_locale = pytest.mark.skipif( + len(_all_locales) <= 1, reason="Need multiple locales for meaningful test") + + +def test_can_set_locale_valid_set(): + # Can set the default locale. + assert tm.can_set_locale("") + + +def test_can_set_locale_invalid_set(): + # Cannot set an invalid locale. + assert not tm.can_set_locale("non-existent_locale") + + +def test_can_set_locale_invalid_get(monkeypatch): + # see gh-22129 + # + # In some cases, an invalid locale can be set, + # but a subsequent getlocale() raises a ValueError. + + def mock_get_locale(): + raise ValueError() + + with monkeypatch.context() as m: + m.setattr(locale, "getlocale", mock_get_locale) + assert not tm.can_set_locale("") + + +def test_get_locales_at_least_one(): + # see gh-9744 + assert len(_all_locales) > 0 + + +@_skip_if_only_one_locale +def test_get_locales_prefix(): + first_locale = _all_locales[0] + assert len(tm.get_locales(prefix=first_locale[:2])) > 0 + + +@_skip_if_only_one_locale +def test_set_locale(): + if com._all_none(_current_locale): + # Not sure why, but on some Travis runs with pytest, + # getlocale() returned (None, None). + pytest.skip("Current locale is not set.") + + locale_override = os.environ.get("LOCALE_OVERRIDE", None) + + if locale_override is None: + lang, enc = "it_CH", "UTF-8" + elif locale_override == "C": + lang, enc = "en_US", "ascii" + else: + lang, enc = locale_override.split(".") + + enc = codecs.lookup(enc).name + new_locale = lang, enc + + if not tm.can_set_locale(new_locale): + msg = "unsupported locale setting" + + with pytest.raises(locale.Error, match=msg): + with tm.set_locale(new_locale): + pass + else: + with tm.set_locale(new_locale) as normalized_locale: + new_lang, new_enc = normalized_locale.split(".") + new_enc = codecs.lookup(enc).name + + normalized_locale = new_lang, new_enc + assert normalized_locale == new_locale + + # Once we exit the "with" statement, locale should be back to what it was. + current_locale = locale.getlocale() + assert current_locale == _current_locale diff --git a/pandas/tests/util/test_move.py b/pandas/tests/util/test_move.py new file mode 100644 index 0000000000000..c12e2f7a167ad --- /dev/null +++ b/pandas/tests/util/test_move.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +import sys +from uuid import uuid4 + +import pytest + +from pandas.compat import PY3, intern +from pandas.util._move import BadMove, move_into_mutable_buffer, stolenbuf + + +def test_cannot_create_instance_of_stolen_buffer(): + # Stolen buffers need to be created through the smart constructor + # "move_into_mutable_buffer," which has a bunch of checks in it. + + msg = "cannot create 'pandas.util._move.stolenbuf' instances" + with pytest.raises(TypeError, match=msg): + stolenbuf() + + +def test_more_than_one_ref(): + # Test case for when we try to use "move_into_mutable_buffer" + # when the object being moved has other references. + + b = b"testing" + + with pytest.raises(BadMove) as e: + def handle_success(type_, value, tb): + assert value.args[0] is b + return type(e).handle_success(e, type_, value, tb) # super + + e.handle_success = handle_success + move_into_mutable_buffer(b) + + +def test_exactly_one_ref(): + # Test case for when the object being moved has exactly one reference. + + b = b"testing" + + # We need to pass an expression on the stack to ensure that there are + # not extra references hanging around. We cannot rewrite this test as + # buf = b[:-3] + # as_stolen_buf = move_into_mutable_buffer(buf) + # because then we would have more than one reference to buf. + as_stolen_buf = move_into_mutable_buffer(b[:-3]) + + # Materialize as byte-array to show that it is mutable. + assert bytearray(as_stolen_buf) == b"test" + + +@pytest.mark.skipif(PY3, reason="bytes objects cannot be interned in PY3") +def test_interned(): + salt = uuid4().hex + + def make_string(): + # We need to actually create a new string so that it has refcount + # one. We use a uuid so that we know the string could not already + # be in the intern table. + return "".join(("testing: ", salt)) + + # This should work, the string has one reference on the stack. + move_into_mutable_buffer(make_string()) + refcount = [None] # nonlocal + + def ref_capture(ob): + # Subtract two because those are the references owned by this frame: + # 1. The local variables of this stack frame. + # 2. The python data stack of this stack frame. + refcount[0] = sys.getrefcount(ob) - 2 + return ob + + with pytest.raises(BadMove, match="testing"): + # If we intern the string, it will still have one reference. Now, + # it is in the intern table, so if other people intern the same + # string while the mutable buffer holds the first string they will + # be the same instance. + move_into_mutable_buffer(ref_capture(intern(make_string()))) # noqa + + assert refcount[0] == 1 diff --git a/pandas/tests/util/test_safe_import.py b/pandas/tests/util/test_safe_import.py new file mode 100644 index 0000000000000..a9c52ef788390 --- /dev/null +++ b/pandas/tests/util/test_safe_import.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +import sys +import types + +import pytest + +import pandas.util._test_decorators as td + + +@pytest.mark.parametrize("name", ["foo", "hello123"]) +def test_safe_import_non_existent(name): + assert not td.safe_import(name) + + +def test_safe_import_exists(): + assert td.safe_import("pandas") + + +@pytest.mark.parametrize("min_version,valid", [ + ("0.0.0", True), + ("99.99.99", False) +]) +def test_safe_import_versions(min_version, valid): + result = td.safe_import("pandas", min_version=min_version) + result = result if valid else not result + assert result + + +@pytest.mark.parametrize("min_version,valid", [ + (None, False), + ("1.0", True), + ("2.0", False) +]) +def test_safe_import_dummy(monkeypatch, min_version, valid): + mod_name = "hello123" + + mod = types.ModuleType(mod_name) + mod.__version__ = "1.5" + + if min_version is not None: + monkeypatch.setitem(sys.modules, mod_name, mod) + + result = td.safe_import(mod_name, min_version=min_version) + result = result if valid else not result + assert result diff --git a/pandas/tests/util/test_util.py b/pandas/tests/util/test_util.py index a6cb54ee43909..e4b2f0a75051a 100644 --- a/pandas/tests/util/test_util.py +++ b/pandas/tests/util/test_util.py @@ -1,532 +1,50 @@ # -*- coding: utf-8 -*- -import codecs -from collections import OrderedDict -import locale -import os -import sys -from uuid import uuid4 - import pytest -from pandas.compat import PY3, intern from pandas.util._decorators import deprecate_kwarg, make_signature -from pandas.util._move import BadMove, move_into_mutable_buffer, stolenbuf -import pandas.util._test_decorators as td -from pandas.util._validators import ( - validate_args, validate_args_and_kwargs, validate_bool_kwarg, - validate_kwargs) +from pandas.util._validators import validate_kwargs -import pandas.core.common as com import pandas.util.testing as tm -class TestDecorators(object): - - def setup_method(self, method): - @deprecate_kwarg('old', 'new') - def _f1(new=False): - return new - - @deprecate_kwarg('old', 'new', {'yes': True, 'no': False}) - def _f2(new=False): - return new - - @deprecate_kwarg('old', 'new', lambda x: x + 1) - def _f3(new=0): - return new - - @deprecate_kwarg('old', None) - def _f4(old=True, unchanged=True): - return old - - self.f1 = _f1 - self.f2 = _f2 - self.f3 = _f3 - self.f4 = _f4 - - def test_deprecate_kwarg(self): - x = 78 - with tm.assert_produces_warning(FutureWarning): - result = self.f1(old=x) - assert result is x - with tm.assert_produces_warning(None): - self.f1(new=x) - - def test_dict_deprecate_kwarg(self): - x = 'yes' - with tm.assert_produces_warning(FutureWarning): - result = self.f2(old=x) - assert result - - def test_missing_deprecate_kwarg(self): - x = 'bogus' - with tm.assert_produces_warning(FutureWarning): - result = self.f2(old=x) - assert result == 'bogus' - - def test_callable_deprecate_kwarg(self): - x = 5 - with tm.assert_produces_warning(FutureWarning): - result = self.f3(old=x) - assert result == x + 1 - with pytest.raises(TypeError): - self.f3(old='hello') - - def test_bad_deprecate_kwarg(self): - with pytest.raises(TypeError): - @deprecate_kwarg('old', 'new', 0) - def f4(new=None): - pass - - def test_deprecate_keyword(self): - x = 9 - with tm.assert_produces_warning(FutureWarning): - result = self.f4(old=x) - assert result is x - with tm.assert_produces_warning(None): - result = self.f4(unchanged=x) - assert result is True - - def test_rands(): r = tm.rands(10) assert(len(r) == 10) -def test_rands_array(): +def test_rands_array_1d(): arr = tm.rands_array(5, size=10) assert(arr.shape == (10,)) assert(len(arr[0]) == 5) + +def test_rands_array_2d(): arr = tm.rands_array(7, size=(10, 10)) assert(arr.shape == (10, 10)) assert(len(arr[1, 1]) == 7) -class TestValidateArgs(object): - fname = 'func' - - def test_bad_min_fname_arg_count(self): - msg = "'max_fname_arg_count' must be non-negative" - with pytest.raises(ValueError, match=msg): - validate_args(self.fname, (None,), -1, 'foo') - - def test_bad_arg_length_max_value_single(self): - args = (None, None) - compat_args = ('foo',) - - min_fname_arg_count = 0 - max_length = len(compat_args) + min_fname_arg_count - actual_length = len(args) + min_fname_arg_count - msg = (r"{fname}\(\) takes at most {max_length} " - r"argument \({actual_length} given\)" - .format(fname=self.fname, max_length=max_length, - actual_length=actual_length)) - - with pytest.raises(TypeError, match=msg): - validate_args(self.fname, args, - min_fname_arg_count, - compat_args) - - def test_bad_arg_length_max_value_multiple(self): - args = (None, None) - compat_args = dict(foo=None) - - min_fname_arg_count = 2 - max_length = len(compat_args) + min_fname_arg_count - actual_length = len(args) + min_fname_arg_count - msg = (r"{fname}\(\) takes at most {max_length} " - r"arguments \({actual_length} given\)" - .format(fname=self.fname, max_length=max_length, - actual_length=actual_length)) - - with pytest.raises(TypeError, match=msg): - validate_args(self.fname, args, - min_fname_arg_count, - compat_args) - - def test_not_all_defaults(self): - bad_arg = 'foo' - msg = ("the '{arg}' parameter is not supported " - r"in the pandas implementation of {func}\(\)". - format(arg=bad_arg, func=self.fname)) - - compat_args = OrderedDict() - compat_args['foo'] = 2 - compat_args['bar'] = -1 - compat_args['baz'] = 3 - - arg_vals = (1, -1, 3) - - for i in range(1, 3): - with pytest.raises(ValueError, match=msg): - validate_args(self.fname, arg_vals[:i], 2, compat_args) - - def test_validation(self): - # No exceptions should be thrown - validate_args(self.fname, (None,), 2, dict(out=None)) - - compat_args = OrderedDict() - compat_args['axis'] = 1 - compat_args['out'] = None - - validate_args(self.fname, (1, None), 2, compat_args) - - -class TestValidateKwargs(object): - fname = 'func' - - def test_bad_kwarg(self): - goodarg = 'f' - badarg = goodarg + 'o' - - compat_args = OrderedDict() - compat_args[goodarg] = 'foo' - compat_args[badarg + 'o'] = 'bar' - kwargs = {goodarg: 'foo', badarg: 'bar'} - msg = (r"{fname}\(\) got an unexpected " - r"keyword argument '{arg}'".format( - fname=self.fname, arg=badarg)) - - with pytest.raises(TypeError, match=msg): - validate_kwargs(self.fname, kwargs, compat_args) - - def test_not_all_none(self): - bad_arg = 'foo' - msg = (r"the '{arg}' parameter is not supported " - r"in the pandas implementation of {func}\(\)". - format(arg=bad_arg, func=self.fname)) - - compat_args = OrderedDict() - compat_args['foo'] = 1 - compat_args['bar'] = 's' - compat_args['baz'] = None - - kwarg_keys = ('foo', 'bar', 'baz') - kwarg_vals = (2, 's', None) - - for i in range(1, 3): - kwargs = dict(zip(kwarg_keys[:i], - kwarg_vals[:i])) - - with pytest.raises(ValueError, match=msg): - validate_kwargs(self.fname, kwargs, compat_args) - - def test_validation(self): - # No exceptions should be thrown - compat_args = OrderedDict() - compat_args['f'] = None - compat_args['b'] = 1 - compat_args['ba'] = 's' - kwargs = dict(f=None, b=1) - validate_kwargs(self.fname, kwargs, compat_args) - - def test_validate_bool_kwarg(self): - arg_names = ['inplace', 'copy'] - invalid_values = [1, "True", [1, 2, 3], 5.0] - valid_values = [True, False, None] - - for name in arg_names: - for value in invalid_values: - msg = ("For argument \"%s\" " - "expected type bool, " - "received type %s" % - (name, type(value).__name__)) - with pytest.raises(ValueError, match=msg): - validate_bool_kwarg(value, name) - - for value in valid_values: - assert validate_bool_kwarg(value, name) == value - - -class TestValidateKwargsAndArgs(object): - fname = 'func' - - def test_invalid_total_length_max_length_one(self): - compat_args = ('foo',) - kwargs = {'foo': 'FOO'} - args = ('FoO', 'BaZ') - - min_fname_arg_count = 0 - max_length = len(compat_args) + min_fname_arg_count - actual_length = len(kwargs) + len(args) + min_fname_arg_count - msg = (r"{fname}\(\) takes at most {max_length} " - r"argument \({actual_length} given\)" - .format(fname=self.fname, max_length=max_length, - actual_length=actual_length)) - - with pytest.raises(TypeError, match=msg): - validate_args_and_kwargs(self.fname, args, kwargs, - min_fname_arg_count, - compat_args) - - def test_invalid_total_length_max_length_multiple(self): - compat_args = ('foo', 'bar', 'baz') - kwargs = {'foo': 'FOO', 'bar': 'BAR'} - args = ('FoO', 'BaZ') - - min_fname_arg_count = 2 - max_length = len(compat_args) + min_fname_arg_count - actual_length = len(kwargs) + len(args) + min_fname_arg_count - msg = (r"{fname}\(\) takes at most {max_length} " - r"arguments \({actual_length} given\)" - .format(fname=self.fname, max_length=max_length, - actual_length=actual_length)) - - with pytest.raises(TypeError, match=msg): - validate_args_and_kwargs(self.fname, args, kwargs, - min_fname_arg_count, - compat_args) - - def test_no_args_with_kwargs(self): - bad_arg = 'bar' - min_fname_arg_count = 2 - - compat_args = OrderedDict() - compat_args['foo'] = -5 - compat_args[bad_arg] = 1 - - msg = (r"the '{arg}' parameter is not supported " - r"in the pandas implementation of {func}\(\)". - format(arg=bad_arg, func=self.fname)) - - args = () - kwargs = {'foo': -5, bad_arg: 2} - with pytest.raises(ValueError, match=msg): - validate_args_and_kwargs(self.fname, args, kwargs, - min_fname_arg_count, compat_args) - - args = (-5, 2) - kwargs = {} - with pytest.raises(ValueError, match=msg): - validate_args_and_kwargs(self.fname, args, kwargs, - min_fname_arg_count, compat_args) - - def test_duplicate_argument(self): - min_fname_arg_count = 2 - compat_args = OrderedDict() - compat_args['foo'] = None - compat_args['bar'] = None - compat_args['baz'] = None - kwargs = {'foo': None, 'bar': None} - args = (None,) # duplicate value for 'foo' - - msg = (r"{fname}\(\) got multiple values for keyword " - r"argument '{arg}'".format(fname=self.fname, arg='foo')) - - with pytest.raises(TypeError, match=msg): - validate_args_and_kwargs(self.fname, args, kwargs, - min_fname_arg_count, - compat_args) - - def test_validation(self): - # No exceptions should be thrown - compat_args = OrderedDict() - compat_args['foo'] = 1 - compat_args['bar'] = None - compat_args['baz'] = -2 - kwargs = {'baz': -2} - args = (1, None) - - min_fname_arg_count = 2 - validate_args_and_kwargs(self.fname, args, kwargs, - min_fname_arg_count, - compat_args) - - -class TestMove(object): - - def test_cannot_create_instance_of_stolenbuffer(self): - """Stolen buffers need to be created through the smart constructor - ``move_into_mutable_buffer`` which has a bunch of checks in it. - """ - msg = "cannot create 'pandas.util._move.stolenbuf' instances" - with pytest.raises(TypeError, match=msg): - stolenbuf() - - def test_more_than_one_ref(self): - """Test case for when we try to use ``move_into_mutable_buffer`` when - the object being moved has other references. - """ - b = b'testing' - - with pytest.raises(BadMove) as e: - def handle_success(type_, value, tb): - assert value.args[0] is b - return type(e).handle_success(e, type_, value, tb) # super - - e.handle_success = handle_success - move_into_mutable_buffer(b) - - def test_exactly_one_ref(self): - """Test case for when the object being moved has exactly one reference. - """ - b = b'testing' - - # We need to pass an expression on the stack to ensure that there are - # not extra references hanging around. We cannot rewrite this test as - # buf = b[:-3] - # as_stolen_buf = move_into_mutable_buffer(buf) - # because then we would have more than one reference to buf. - as_stolen_buf = move_into_mutable_buffer(b[:-3]) - - # materialize as bytearray to show that it is mutable - assert bytearray(as_stolen_buf) == b'test' - - @pytest.mark.skipif(PY3, reason='bytes objects cannot be interned in py3') - def test_interned(self): - salt = uuid4().hex - - def make_string(): - # We need to actually create a new string so that it has refcount - # one. We use a uuid so that we know the string could not already - # be in the intern table. - return ''.join(('testing: ', salt)) - - # This should work, the string has one reference on the stack. - move_into_mutable_buffer(make_string()) - - refcount = [None] # nonlocal - - def ref_capture(ob): - # Subtract two because those are the references owned by this - # frame: - # 1. The local variables of this stack frame. - # 2. The python data stack of this stack frame. - refcount[0] = sys.getrefcount(ob) - 2 - return ob - - with pytest.raises(BadMove): - # If we intern the string it will still have one reference but now - # it is in the intern table so if other people intern the same - # string while the mutable buffer holds the first string they will - # be the same instance. - move_into_mutable_buffer(ref_capture(intern(make_string()))) # noqa - - assert refcount[0] == 1 - - -def test_numpy_errstate_is_default(): +def test_numpy_err_state_is_default(): # The defaults since numpy 1.6.0 - expected = {'over': 'warn', 'divide': 'warn', 'invalid': 'warn', - 'under': 'ignore'} + expected = {"over": "warn", "divide": "warn", + "invalid": "warn", "under": "ignore"} import numpy as np - from pandas.compat import numpy # noqa - # The errstate should be unchanged after that import. - assert np.geterr() == expected - - -@td.skip_if_windows -class TestLocaleUtils(object): - - @classmethod - def setup_class(cls): - cls.locales = tm.get_locales() - cls.current_locale = locale.getlocale() - - if not cls.locales: - pytest.skip("No locales found") - - @classmethod - def teardown_class(cls): - del cls.locales - del cls.current_locale - - def test_can_set_locale_valid_set(self): - # Setting the default locale should return True - assert tm.can_set_locale('') is True - - def test_can_set_locale_invalid_set(self): - # Setting an invalid locale should return False - assert tm.can_set_locale('non-existent_locale') is False - - def test_can_set_locale_invalid_get(self, monkeypatch): - # In some cases, an invalid locale can be set, - # but a subsequent getlocale() raises a ValueError - # See GH 22129 - - def mockgetlocale(): - raise ValueError() - - with monkeypatch.context() as m: - m.setattr(locale, 'getlocale', mockgetlocale) - assert tm.can_set_locale('') is False - - def test_get_locales(self): - # all systems should have at least a single locale - # GH9744 - assert len(tm.get_locales()) > 0 - def test_get_locales_prefix(self): - if len(self.locales) == 1: - pytest.skip("Only a single locale found, no point in " - "trying to test filtering locale prefixes") - first_locale = self.locales[0] - assert len(tm.get_locales(prefix=first_locale[:2])) > 0 - - def test_set_locale(self): - if len(self.locales) == 1: - pytest.skip("Only a single locale found, no point in " - "trying to test setting another locale") - - if com._all_none(*self.current_locale): - # Not sure why, but on some travis runs with pytest, - # getlocale() returned (None, None). - pytest.skip("Current locale is not set.") - - locale_override = os.environ.get('LOCALE_OVERRIDE', None) - - if locale_override is None: - lang, enc = 'it_CH', 'UTF-8' - elif locale_override == 'C': - lang, enc = 'en_US', 'ascii' - else: - lang, enc = locale_override.split('.') - - enc = codecs.lookup(enc).name - new_locale = lang, enc - - if not tm.can_set_locale(new_locale): - with pytest.raises(locale.Error): - with tm.set_locale(new_locale): - pass - else: - with tm.set_locale(new_locale) as normalized_locale: - new_lang, new_enc = normalized_locale.split('.') - new_enc = codecs.lookup(enc).name - normalized_locale = new_lang, new_enc - assert normalized_locale == new_locale - - current_locale = locale.getlocale() - assert current_locale == self.current_locale - - -def test_make_signature(): - # See GH 17608 - # Case where the func does not have default kwargs - sig = make_signature(validate_kwargs) - assert sig == (['fname', 'kwargs', 'compat_args'], - ['fname', 'kwargs', 'compat_args']) - - # Case where the func does have default kwargs - sig = make_signature(deprecate_kwarg) - assert sig == (['old_arg_name', 'new_arg_name', - 'mapping=None', 'stacklevel=2'], - ['old_arg_name', 'new_arg_name', 'mapping', 'stacklevel']) - - -def test_safe_import(monkeypatch): - assert not td.safe_import("foo") - assert not td.safe_import("pandas", min_version="99.99.99") + # The error state should be unchanged after that import. + assert np.geterr() == expected - # Create dummy module to be imported - import types - import sys - mod_name = "hello123" - mod = types.ModuleType(mod_name) - mod.__version__ = "1.5" - assert not td.safe_import(mod_name) - monkeypatch.setitem(sys.modules, mod_name, mod) - assert not td.safe_import(mod_name, min_version="2.0") - assert td.safe_import(mod_name, min_version="1.0") +@pytest.mark.parametrize("func,expected", [ + # Case where the func does not have default kwargs. + (validate_kwargs, (["fname", "kwargs", "compat_args"], + ["fname", "kwargs", "compat_args"])), + + # Case where the func does have default kwargs. + (deprecate_kwarg, (["old_arg_name", "new_arg_name", + "mapping=None", "stacklevel=2"], + ["old_arg_name", "new_arg_name", + "mapping", "stacklevel"])) +]) +def test_make_signature(func, expected): + # see gh-17608 + assert make_signature(func) == expected diff --git a/pandas/tests/util/test_validate_args.py b/pandas/tests/util/test_validate_args.py new file mode 100644 index 0000000000000..ca71b0c9d2522 --- /dev/null +++ b/pandas/tests/util/test_validate_args.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from collections import OrderedDict + +import pytest + +from pandas.util._validators import validate_args + +_fname = "func" + + +def test_bad_min_fname_arg_count(): + msg = "'max_fname_arg_count' must be non-negative" + + with pytest.raises(ValueError, match=msg): + validate_args(_fname, (None,), -1, "foo") + + +def test_bad_arg_length_max_value_single(): + args = (None, None) + compat_args = ("foo",) + + min_fname_arg_count = 0 + max_length = len(compat_args) + min_fname_arg_count + actual_length = len(args) + min_fname_arg_count + msg = (r"{fname}\(\) takes at most {max_length} " + r"argument \({actual_length} given\)" + .format(fname=_fname, max_length=max_length, + actual_length=actual_length)) + + with pytest.raises(TypeError, match=msg): + validate_args(_fname, args, min_fname_arg_count, compat_args) + + +def test_bad_arg_length_max_value_multiple(): + args = (None, None) + compat_args = dict(foo=None) + + min_fname_arg_count = 2 + max_length = len(compat_args) + min_fname_arg_count + actual_length = len(args) + min_fname_arg_count + msg = (r"{fname}\(\) takes at most {max_length} " + r"arguments \({actual_length} given\)" + .format(fname=_fname, max_length=max_length, + actual_length=actual_length)) + + with pytest.raises(TypeError, match=msg): + validate_args(_fname, args, min_fname_arg_count, compat_args) + + +@pytest.mark.parametrize("i", range(1, 3)) +def test_not_all_defaults(i): + bad_arg = "foo" + msg = ("the '{arg}' parameter is not supported " + r"in the pandas implementation of {func}\(\)". + format(arg=bad_arg, func=_fname)) + + compat_args = OrderedDict() + compat_args["foo"] = 2 + compat_args["bar"] = -1 + compat_args["baz"] = 3 + + arg_vals = (1, -1, 3) + + with pytest.raises(ValueError, match=msg): + validate_args(_fname, arg_vals[:i], 2, compat_args) + + +def test_validation(): + # No exceptions should be raised. + validate_args(_fname, (None,), 2, dict(out=None)) + + compat_args = OrderedDict() + compat_args["axis"] = 1 + compat_args["out"] = None + + validate_args(_fname, (1, None), 2, compat_args) diff --git a/pandas/tests/util/test_validate_args_and_kwargs.py b/pandas/tests/util/test_validate_args_and_kwargs.py new file mode 100644 index 0000000000000..c3c0b3dedc085 --- /dev/null +++ b/pandas/tests/util/test_validate_args_and_kwargs.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +from collections import OrderedDict + +import pytest + +from pandas.util._validators import validate_args_and_kwargs + +_fname = "func" + + +def test_invalid_total_length_max_length_one(): + compat_args = ("foo",) + kwargs = {"foo": "FOO"} + args = ("FoO", "BaZ") + + min_fname_arg_count = 0 + max_length = len(compat_args) + min_fname_arg_count + actual_length = len(kwargs) + len(args) + min_fname_arg_count + + msg = (r"{fname}\(\) takes at most {max_length} " + r"argument \({actual_length} given\)" + .format(fname=_fname, max_length=max_length, + actual_length=actual_length)) + + with pytest.raises(TypeError, match=msg): + validate_args_and_kwargs(_fname, args, kwargs, + min_fname_arg_count, + compat_args) + + +def test_invalid_total_length_max_length_multiple(): + compat_args = ("foo", "bar", "baz") + kwargs = {"foo": "FOO", "bar": "BAR"} + args = ("FoO", "BaZ") + + min_fname_arg_count = 2 + max_length = len(compat_args) + min_fname_arg_count + actual_length = len(kwargs) + len(args) + min_fname_arg_count + + msg = (r"{fname}\(\) takes at most {max_length} " + r"arguments \({actual_length} given\)" + .format(fname=_fname, max_length=max_length, + actual_length=actual_length)) + + with pytest.raises(TypeError, match=msg): + validate_args_and_kwargs(_fname, args, kwargs, + min_fname_arg_count, + compat_args) + + +@pytest.mark.parametrize("args,kwargs", [ + ((), {"foo": -5, "bar": 2}), + ((-5, 2), {}) +]) +def test_missing_args_or_kwargs(args, kwargs): + bad_arg = "bar" + min_fname_arg_count = 2 + + compat_args = OrderedDict() + compat_args["foo"] = -5 + compat_args[bad_arg] = 1 + + msg = (r"the '{arg}' parameter is not supported " + r"in the pandas implementation of {func}\(\)". + format(arg=bad_arg, func=_fname)) + + with pytest.raises(ValueError, match=msg): + validate_args_and_kwargs(_fname, args, kwargs, + min_fname_arg_count, compat_args) + + +def test_duplicate_argument(): + min_fname_arg_count = 2 + + compat_args = OrderedDict() + compat_args["foo"] = None + compat_args["bar"] = None + compat_args["baz"] = None + + kwargs = {"foo": None, "bar": None} + args = (None,) # duplicate value for "foo" + + msg = (r"{fname}\(\) got multiple values for keyword " + r"argument '{arg}'".format(fname=_fname, arg="foo")) + + with pytest.raises(TypeError, match=msg): + validate_args_and_kwargs(_fname, args, kwargs, + min_fname_arg_count, + compat_args) + + +def test_validation(): + # No exceptions should be raised. + compat_args = OrderedDict() + compat_args["foo"] = 1 + compat_args["bar"] = None + compat_args["baz"] = -2 + kwargs = {"baz": -2} + + args = (1, None) + min_fname_arg_count = 2 + + validate_args_and_kwargs(_fname, args, kwargs, + min_fname_arg_count, + compat_args) diff --git a/pandas/tests/util/test_validate_kwargs.py b/pandas/tests/util/test_validate_kwargs.py new file mode 100644 index 0000000000000..f36818ddfc9a8 --- /dev/null +++ b/pandas/tests/util/test_validate_kwargs.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +from collections import OrderedDict + +import pytest + +from pandas.util._validators import validate_bool_kwarg, validate_kwargs + +_fname = "func" + + +def test_bad_kwarg(): + good_arg = "f" + bad_arg = good_arg + "o" + + compat_args = OrderedDict() + compat_args[good_arg] = "foo" + compat_args[bad_arg + "o"] = "bar" + kwargs = {good_arg: "foo", bad_arg: "bar"} + + msg = (r"{fname}\(\) got an unexpected " + r"keyword argument '{arg}'".format(fname=_fname, arg=bad_arg)) + + with pytest.raises(TypeError, match=msg): + validate_kwargs(_fname, kwargs, compat_args) + + +@pytest.mark.parametrize("i", range(1, 3)) +def test_not_all_none(i): + bad_arg = "foo" + msg = (r"the '{arg}' parameter is not supported " + r"in the pandas implementation of {func}\(\)". + format(arg=bad_arg, func=_fname)) + + compat_args = OrderedDict() + compat_args["foo"] = 1 + compat_args["bar"] = "s" + compat_args["baz"] = None + + kwarg_keys = ("foo", "bar", "baz") + kwarg_vals = (2, "s", None) + + kwargs = dict(zip(kwarg_keys[:i], kwarg_vals[:i])) + + with pytest.raises(ValueError, match=msg): + validate_kwargs(_fname, kwargs, compat_args) + + +def test_validation(): + # No exceptions should be raised. + compat_args = OrderedDict() + compat_args["f"] = None + compat_args["b"] = 1 + compat_args["ba"] = "s" + + kwargs = dict(f=None, b=1) + validate_kwargs(_fname, kwargs, compat_args) + + +@pytest.mark.parametrize("name", ["inplace", "copy"]) +@pytest.mark.parametrize("value", [1, "True", [1, 2, 3], 5.0]) +def test_validate_bool_kwarg_fail(name, value): + msg = ("For argument \"%s\" expected type bool, received type %s" % + (name, type(value).__name__)) + + with pytest.raises(ValueError, match=msg): + validate_bool_kwarg(value, name) + + +@pytest.mark.parametrize("name", ["inplace", "copy"]) +@pytest.mark.parametrize("value", [True, False, None]) +def test_validate_bool_kwarg(name, value): + assert validate_bool_kwarg(value, name) == value