From d7084616472ee39faa2758117dc7d6707b3a8bb3 Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Sun, 3 Mar 2019 01:41:53 +0000 Subject: [PATCH] =?UTF-8?q?STY:=20use=20pytest.raises=20context=20manager?= =?UTF-8?q?=20(arithmetic,=20arrays,=20computati=E2=80=A6=20(#25504)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pandas/tests/arithmetic/test_timedelta64.py | 66 ++++++++++---- .../arrays/categorical/test_analytics.py | 29 +++--- .../arrays/categorical/test_operators.py | 88 +++++++++++++------ pandas/tests/arrays/sparse/test_libsparse.py | 10 ++- pandas/tests/computation/test_eval.py | 74 ++++++++++------ pandas/tests/dtypes/test_common.py | 15 ++-- pandas/tests/dtypes/test_dtypes.py | 18 ++-- 7 files changed, 205 insertions(+), 95 deletions(-) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index c31d7acad3111..0faed74d4a021 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -205,10 +205,20 @@ def test_subtraction_ops(self): td = Timedelta('1 days') dt = Timestamp('20130101') - pytest.raises(TypeError, lambda: tdi - dt) - pytest.raises(TypeError, lambda: tdi - dti) - pytest.raises(TypeError, lambda: td - dt) - pytest.raises(TypeError, lambda: td - dti) + msg = "cannot subtract a datelike from a TimedeltaArray" + with pytest.raises(TypeError, match=msg): + tdi - dt + with pytest.raises(TypeError, match=msg): + tdi - dti + + msg = (r"descriptor '__sub__' requires a 'datetime\.datetime' object" + " but received a 'Timedelta'") + with pytest.raises(TypeError, match=msg): + td - dt + + msg = "bad operand type for unary -: 'DatetimeArray'" + with pytest.raises(TypeError, match=msg): + td - dti result = dt - dti expected = TimedeltaIndex(['0 days', '-1 days', '-2 days'], name='bar') @@ -265,19 +275,38 @@ def _check(result, expected): _check(result, expected) # tz mismatches - pytest.raises(TypeError, lambda: dt_tz - ts) - pytest.raises(TypeError, lambda: dt_tz - dt) - pytest.raises(TypeError, lambda: dt_tz - ts_tz2) - pytest.raises(TypeError, lambda: dt - dt_tz) - pytest.raises(TypeError, lambda: ts - dt_tz) - pytest.raises(TypeError, lambda: ts_tz2 - ts) - pytest.raises(TypeError, lambda: ts_tz2 - dt) - pytest.raises(TypeError, lambda: ts_tz - ts_tz2) + msg = ("Timestamp subtraction must have the same timezones or no" + " timezones") + with pytest.raises(TypeError, match=msg): + dt_tz - ts + msg = "can't subtract offset-naive and offset-aware datetimes" + with pytest.raises(TypeError, match=msg): + dt_tz - dt + msg = ("Timestamp subtraction must have the same timezones or no" + " timezones") + with pytest.raises(TypeError, match=msg): + dt_tz - ts_tz2 + msg = "can't subtract offset-naive and offset-aware datetimes" + with pytest.raises(TypeError, match=msg): + dt - dt_tz + msg = ("Timestamp subtraction must have the same timezones or no" + " timezones") + with pytest.raises(TypeError, match=msg): + ts - dt_tz + with pytest.raises(TypeError, match=msg): + ts_tz2 - ts + with pytest.raises(TypeError, match=msg): + ts_tz2 - dt + with pytest.raises(TypeError, match=msg): + ts_tz - ts_tz2 # with dti - pytest.raises(TypeError, lambda: dti - ts_tz) - pytest.raises(TypeError, lambda: dti_tz - ts) - pytest.raises(TypeError, lambda: dti_tz - ts_tz2) + with pytest.raises(TypeError, match=msg): + dti - ts_tz + with pytest.raises(TypeError, match=msg): + dti_tz - ts + with pytest.raises(TypeError, match=msg): + dti_tz - ts_tz2 result = dti_tz - dt_tz expected = TimedeltaIndex(['0 days', '1 days', '2 days']) @@ -349,8 +378,11 @@ def test_addition_ops(self): tm.assert_index_equal(result, expected) # unequal length - pytest.raises(ValueError, lambda: tdi + dti[0:1]) - pytest.raises(ValueError, lambda: tdi[0:1] + dti) + msg = "cannot add indices of unequal length" + with pytest.raises(ValueError, match=msg): + tdi + dti[0:1] + with pytest.raises(ValueError, match=msg): + tdi[0:1] + dti # random indexes with pytest.raises(NullFrequencyError): diff --git a/pandas/tests/arrays/categorical/test_analytics.py b/pandas/tests/arrays/categorical/test_analytics.py index 5efcd527de8d8..7ce82d5bcdded 100644 --- a/pandas/tests/arrays/categorical/test_analytics.py +++ b/pandas/tests/arrays/categorical/test_analytics.py @@ -18,8 +18,11 @@ def test_min_max(self): # unordered cats have no min/max cat = Categorical(["a", "b", "c", "d"], ordered=False) - pytest.raises(TypeError, lambda: cat.min()) - pytest.raises(TypeError, lambda: cat.max()) + msg = "Categorical is not ordered for operation {}" + with pytest.raises(TypeError, match=msg.format('min')): + cat.min() + with pytest.raises(TypeError, match=msg.format('max')): + cat.max() cat = Categorical(["a", "b", "c", "d"], ordered=True) _min = cat.min() @@ -108,18 +111,24 @@ def test_searchsorted(self): tm.assert_numpy_array_equal(res_ser, exp) # Searching for a single value that is not from the Categorical - pytest.raises(KeyError, lambda: c1.searchsorted('cucumber')) - pytest.raises(KeyError, lambda: s1.searchsorted('cucumber')) + msg = r"Value\(s\) to be inserted must be in categories" + with pytest.raises(KeyError, match=msg): + c1.searchsorted('cucumber') + with pytest.raises(KeyError, match=msg): + s1.searchsorted('cucumber') # Searching for multiple values one of each is not from the Categorical - pytest.raises(KeyError, - lambda: c1.searchsorted(['bread', 'cucumber'])) - pytest.raises(KeyError, - lambda: s1.searchsorted(['bread', 'cucumber'])) + with pytest.raises(KeyError, match=msg): + c1.searchsorted(['bread', 'cucumber']) + with pytest.raises(KeyError, match=msg): + s1.searchsorted(['bread', 'cucumber']) # searchsorted call for unordered Categorical - pytest.raises(ValueError, lambda: c2.searchsorted('apple')) - pytest.raises(ValueError, lambda: s2.searchsorted('apple')) + msg = "Categorical not ordered" + with pytest.raises(ValueError, match=msg): + c2.searchsorted('apple') + with pytest.raises(ValueError, match=msg): + s2.searchsorted('apple') def test_unique(self): # categories are reordered based on value when ordered=False diff --git a/pandas/tests/arrays/categorical/test_operators.py b/pandas/tests/arrays/categorical/test_operators.py index b2965bbcc456a..e1264722aedcd 100644 --- a/pandas/tests/arrays/categorical/test_operators.py +++ b/pandas/tests/arrays/categorical/test_operators.py @@ -4,6 +4,8 @@ import numpy as np import pytest +from pandas.compat import PY2 + import pandas as pd from pandas import Categorical, DataFrame, Series, date_range from pandas.tests.arrays.categorical.common import TestCategorical @@ -17,6 +19,7 @@ def test_categories_none_comparisons(self): 'a', 'c', 'c', 'c'], ordered=True) tm.assert_categorical_equal(factor, self.factor) + @pytest.mark.skipif(PY2, reason="pytest.raises match regex fails") def test_comparisons(self): result = self.factor[self.factor == 'a'] @@ -95,16 +98,24 @@ def test_comparisons(self): # comparison (in both directions) with Series will raise s = Series(["b", "b", "b"]) - pytest.raises(TypeError, lambda: cat > s) - pytest.raises(TypeError, lambda: cat_rev > s) - pytest.raises(TypeError, lambda: s < cat) - pytest.raises(TypeError, lambda: s < cat_rev) + msg = ("Cannot compare a Categorical for op __gt__ with type" + r" ") + with pytest.raises(TypeError, match=msg): + cat > s + with pytest.raises(TypeError, match=msg): + cat_rev > s + with pytest.raises(TypeError, match=msg): + s < cat + with pytest.raises(TypeError, match=msg): + s < cat_rev # comparison with numpy.array will raise in both direction, but only on # newer numpy versions a = np.array(["b", "b", "b"]) - pytest.raises(TypeError, lambda: cat > a) - pytest.raises(TypeError, lambda: cat_rev > a) + with pytest.raises(TypeError, match=msg): + cat > a + with pytest.raises(TypeError, match=msg): + cat_rev > a # Make sure that unequal comparison take the categories order in # account @@ -163,16 +174,23 @@ def test_comparison_with_unknown_scalars(self): # for unequal comps, but not for equal/not equal cat = Categorical([1, 2, 3], ordered=True) - pytest.raises(TypeError, lambda: cat < 4) - pytest.raises(TypeError, lambda: cat > 4) - pytest.raises(TypeError, lambda: 4 < cat) - pytest.raises(TypeError, lambda: 4 > cat) + msg = ("Cannot compare a Categorical for op __{}__ with a scalar," + " which is not a category") + with pytest.raises(TypeError, match=msg.format('lt')): + cat < 4 + with pytest.raises(TypeError, match=msg.format('gt')): + cat > 4 + with pytest.raises(TypeError, match=msg.format('gt')): + 4 < cat + with pytest.raises(TypeError, match=msg.format('lt')): + 4 > cat tm.assert_numpy_array_equal(cat == 4, np.array([False, False, False])) tm.assert_numpy_array_equal(cat != 4, np.array([True, True, True])) + @pytest.mark.skipif(PY2, reason="pytest.raises match regex fails") @pytest.mark.parametrize('data,reverse,base', [ (list("abc"), list("cba"), list("bbb")), ([1, 2, 3], [3, 2, 1], [2, 2, 2])] @@ -219,16 +237,26 @@ def test_comparisons(self, data, reverse, base): # categorical cannot be compared to Series or numpy array, and also # not the other way around - pytest.raises(TypeError, lambda: cat > s) - pytest.raises(TypeError, lambda: cat_rev > s) - pytest.raises(TypeError, lambda: cat > a) - pytest.raises(TypeError, lambda: cat_rev > a) + msg = ("Cannot compare a Categorical for op __gt__ with type" + r" ") + with pytest.raises(TypeError, match=msg): + cat > s + with pytest.raises(TypeError, match=msg): + cat_rev > s + with pytest.raises(TypeError, match=msg): + cat > a + with pytest.raises(TypeError, match=msg): + cat_rev > a - pytest.raises(TypeError, lambda: s < cat) - pytest.raises(TypeError, lambda: s < cat_rev) + with pytest.raises(TypeError, match=msg): + s < cat + with pytest.raises(TypeError, match=msg): + s < cat_rev - pytest.raises(TypeError, lambda: a < cat) - pytest.raises(TypeError, lambda: a < cat_rev) + with pytest.raises(TypeError, match=msg): + a < cat + with pytest.raises(TypeError, match=msg): + a < cat_rev @pytest.mark.parametrize('ctor', [ lambda *args, **kwargs: Categorical(*args, **kwargs), @@ -287,16 +315,21 @@ def test_numeric_like_ops(self): right=False, labels=cat_labels) # numeric ops should not succeed - for op in ['__add__', '__sub__', '__mul__', '__truediv__']: - pytest.raises(TypeError, - lambda: getattr(df, op)(df)) + for op, str_rep in [('__add__', r'\+'), + ('__sub__', '-'), + ('__mul__', r'\*'), + ('__truediv__', '/')]: + msg = r"Series cannot perform the operation {}".format(str_rep) + with pytest.raises(TypeError, match=msg): + getattr(df, op)(df) # reduction ops should not succeed (unless specifically defined, e.g. # min/max) s = df['value_group'] for op in ['kurt', 'skew', 'var', 'std', 'mean', 'sum', 'median']: - pytest.raises(TypeError, - lambda: getattr(s, op)(numeric_only=False)) + msg = "Categorical cannot perform the operation {}".format(op) + with pytest.raises(TypeError, match=msg): + getattr(s, op)(numeric_only=False) # mad technically works because it takes always the numeric data @@ -306,8 +339,13 @@ def test_numeric_like_ops(self): np.sum(s) # numeric ops on a Series - for op in ['__add__', '__sub__', '__mul__', '__truediv__']: - pytest.raises(TypeError, lambda: getattr(s, op)(2)) + for op, str_rep in [('__add__', r'\+'), + ('__sub__', '-'), + ('__mul__', r'\*'), + ('__truediv__', '/')]: + msg = r"Series cannot perform the operation {}".format(str_rep) + with pytest.raises(TypeError, match=msg): + getattr(s, op)(2) # invalid ufunc with pytest.raises(TypeError): diff --git a/pandas/tests/arrays/sparse/test_libsparse.py b/pandas/tests/arrays/sparse/test_libsparse.py index 6e9d790bf85f3..2cbe7d9ea084c 100644 --- a/pandas/tests/arrays/sparse/test_libsparse.py +++ b/pandas/tests/arrays/sparse/test_libsparse.py @@ -449,11 +449,13 @@ def test_check_integrity(self): # also OK even though empty index = BlockIndex(1, locs, lengths) # noqa - # block extend beyond end - pytest.raises(Exception, BlockIndex, 10, [5], [10]) + msg = "Block 0 extends beyond end" + with pytest.raises(ValueError, match=msg): + BlockIndex(10, [5], [10]) - # block overlap - pytest.raises(Exception, BlockIndex, 10, [2, 5], [5, 3]) + msg = "Block 0 overlaps" + with pytest.raises(ValueError, match=msg): + BlockIndex(10, [2, 5], [5, 3]) def test_to_int_index(self): locs = [0, 10] diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index c1ba15f428eb7..a14d8e4471c23 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -285,10 +285,14 @@ def check_operands(left, right, cmp_op): def check_simple_cmp_op(self, lhs, cmp1, rhs): ex = 'lhs {0} rhs'.format(cmp1) + msg = (r"only list-like( or dict-like)? objects are allowed to be" + r" passed to (DataFrame\.)?isin\(\), you passed a" + r" (\[|')bool(\]|')|" + "argument of type 'bool' is not iterable") if cmp1 in ('in', 'not in') and not is_list_like(rhs): - pytest.raises(TypeError, pd.eval, ex, engine=self.engine, - parser=self.parser, local_dict={'lhs': lhs, - 'rhs': rhs}) + with pytest.raises(TypeError, match=msg): + pd.eval(ex, engine=self.engine, parser=self.parser, + local_dict={'lhs': lhs, 'rhs': rhs}) else: expected = _eval_single_bin(lhs, cmp1, rhs, self.engine) result = pd.eval(ex, engine=self.engine, parser=self.parser) @@ -341,9 +345,11 @@ def check_floor_division(self, lhs, arith1, rhs): expected = lhs // rhs self.check_equal(res, expected) else: - pytest.raises(TypeError, pd.eval, ex, - local_dict={'lhs': lhs, 'rhs': rhs}, - engine=self.engine, parser=self.parser) + msg = (r"unsupported operand type\(s\) for //: 'VariableNode' and" + " 'VariableNode'") + with pytest.raises(TypeError, match=msg): + pd.eval(ex, local_dict={'lhs': lhs, 'rhs': rhs}, + engine=self.engine, parser=self.parser) def get_expected_pow_result(self, lhs, rhs): try: @@ -396,10 +402,14 @@ def check_compound_invert_op(self, lhs, cmp1, rhs): skip_these = 'in', 'not in' ex = '~(lhs {0} rhs)'.format(cmp1) + msg = (r"only list-like( or dict-like)? objects are allowed to be" + r" passed to (DataFrame\.)?isin\(\), you passed a" + r" (\[|')float(\]|')|" + "argument of type 'float' is not iterable") if is_scalar(rhs) and cmp1 in skip_these: - pytest.raises(TypeError, pd.eval, ex, engine=self.engine, - parser=self.parser, local_dict={'lhs': lhs, - 'rhs': rhs}) + with pytest.raises(TypeError, match=msg): + pd.eval(ex, engine=self.engine, parser=self.parser, + local_dict={'lhs': lhs, 'rhs': rhs}) else: # compound if is_scalar(lhs) and is_scalar(rhs): @@ -1101,8 +1111,9 @@ def test_simple_arith_ops(self): ex3 = '1 {0} (x + 1)'.format(op) if op in ('in', 'not in'): - pytest.raises(TypeError, pd.eval, ex, - engine=self.engine, parser=self.parser) + msg = "argument of type 'int' is not iterable" + with pytest.raises(TypeError, match=msg): + pd.eval(ex, engine=self.engine, parser=self.parser) else: expec = _eval_single_bin(1, op, 1, self.engine) x = self.eval(ex, engine=self.engine, parser=self.parser) @@ -1236,19 +1247,25 @@ def test_assignment_fails(self): df = DataFrame(np.random.randn(5, 3), columns=list('abc')) df2 = DataFrame(np.random.randn(5, 3)) expr1 = 'df = df2' - pytest.raises(ValueError, self.eval, expr1, - local_dict={'df': df, 'df2': df2}) + msg = "cannot assign without a target object" + with pytest.raises(ValueError, match=msg): + self.eval(expr1, local_dict={'df': df, 'df2': df2}) def test_assignment_column(self): df = DataFrame(np.random.randn(5, 2), columns=list('ab')) orig_df = df.copy() # multiple assignees - pytest.raises(SyntaxError, df.eval, 'd c = a + b') + with pytest.raises(SyntaxError, match="invalid syntax"): + df.eval('d c = a + b') # invalid assignees - pytest.raises(SyntaxError, df.eval, 'd,c = a + b') - pytest.raises(SyntaxError, df.eval, 'Timestamp("20131001") = a + b') + msg = "left hand side of an assignment must be a single name" + with pytest.raises(SyntaxError, match=msg): + df.eval('d,c = a + b') + msg = "can't assign to function call" + with pytest.raises(SyntaxError, match=msg): + df.eval('Timestamp("20131001") = a + b') # single assignment - existing variable expected = orig_df.copy() @@ -1291,7 +1308,9 @@ def f(): # multiple assignment df = orig_df.copy() df.eval('c = a + b', inplace=True) - pytest.raises(SyntaxError, df.eval, 'c = a = b') + msg = "can only assign a single expression" + with pytest.raises(SyntaxError, match=msg): + df.eval('c = a = b') # explicit targets df = orig_df.copy() @@ -1545,21 +1564,24 @@ def test_check_many_exprs(self): def test_fails_and(self): df = DataFrame(np.random.randn(5, 3)) - pytest.raises(NotImplementedError, pd.eval, 'df > 2 and df > 3', - local_dict={'df': df}, parser=self.parser, - engine=self.engine) + msg = "'BoolOp' nodes are not implemented" + with pytest.raises(NotImplementedError, match=msg): + pd.eval('df > 2 and df > 3', local_dict={'df': df}, + parser=self.parser, engine=self.engine) def test_fails_or(self): df = DataFrame(np.random.randn(5, 3)) - pytest.raises(NotImplementedError, pd.eval, 'df > 2 or df > 3', - local_dict={'df': df}, parser=self.parser, - engine=self.engine) + msg = "'BoolOp' nodes are not implemented" + with pytest.raises(NotImplementedError, match=msg): + pd.eval('df > 2 or df > 3', local_dict={'df': df}, + parser=self.parser, engine=self.engine) def test_fails_not(self): df = DataFrame(np.random.randn(5, 3)) - pytest.raises(NotImplementedError, pd.eval, 'not df > 2', - local_dict={'df': df}, parser=self.parser, - engine=self.engine) + msg = "'Not' nodes are not implemented" + with pytest.raises(NotImplementedError, match=msg): + pd.eval('not df > 2', local_dict={'df': df}, parser=self.parser, + engine=self.engine) def test_fails_ampersand(self): df = DataFrame(np.random.randn(5, 3)) # noqa diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index 62e96fd39a759..5c1f6ff405b3b 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -607,13 +607,16 @@ def test__get_dtype(input_param, result): assert com._get_dtype(input_param) == result -@pytest.mark.parametrize('input_param', [None, - 1, 1.2, - 'random string', - pd.DataFrame([1, 2])]) -def test__get_dtype_fails(input_param): +@pytest.mark.parametrize('input_param,expected_error_message', [ + (None, "Cannot deduce dtype from null object"), + (1, "data type not understood"), + (1.2, "data type not understood"), + ('random string', "data type 'random string' not understood"), + (pd.DataFrame([1, 2]), "data type not understood")]) +def test__get_dtype_fails(input_param, expected_error_message): # python objects - pytest.raises(TypeError, com._get_dtype, input_param) + with pytest.raises(TypeError, match=expected_error_message): + com._get_dtype(input_param) @pytest.mark.parametrize('input_param,result', [ diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index 1c1442d6f2f23..4366f610871ff 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -38,7 +38,8 @@ def test_equality_invalid(self): assert not is_dtype_equal(self.dtype, np.int64) def test_numpy_informed(self): - pytest.raises(TypeError, np.dtype, self.dtype) + with pytest.raises(TypeError, match="data type not understood"): + np.dtype(self.dtype) assert not self.dtype == np.str_ assert not np.str_ == self.dtype @@ -87,8 +88,9 @@ def test_equality(self): def test_construction_from_string(self): result = CategoricalDtype.construct_from_string('category') assert is_dtype_equal(self.dtype, result) - pytest.raises( - TypeError, lambda: CategoricalDtype.construct_from_string('foo')) + msg = "cannot construct a CategoricalDtype" + with pytest.raises(TypeError, match=msg): + CategoricalDtype.construct_from_string('foo') def test_constructor_invalid(self): msg = "Parameter 'categories' must be list-like" @@ -202,8 +204,9 @@ def test_hash_vs_equality(self): assert hash(dtype2) != hash(dtype4) def test_construction(self): - pytest.raises(ValueError, - lambda: DatetimeTZDtype('ms', 'US/Eastern')) + msg = "DatetimeTZDtype only supports ns units" + with pytest.raises(ValueError, match=msg): + DatetimeTZDtype('ms', 'US/Eastern') def test_subclass(self): a = DatetimeTZDtype.construct_from_string('datetime64[ns, US/Eastern]') @@ -226,8 +229,9 @@ def test_construction_from_string(self): result = DatetimeTZDtype.construct_from_string( 'datetime64[ns, US/Eastern]') assert is_dtype_equal(self.dtype, result) - pytest.raises(TypeError, - lambda: DatetimeTZDtype.construct_from_string('foo')) + msg = "Could not construct DatetimeTZDtype from 'foo'" + with pytest.raises(TypeError, match=msg): + DatetimeTZDtype.construct_from_string('foo') def test_construct_from_string_raises(self): with pytest.raises(TypeError, match="notatz"):