Skip to content

TST: Implement maximally-specific/strict fixtures for arithmetic tests #23734

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 46 additions & 24 deletions pandas/tests/arithmetic/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
import pandas as pd

from pandas.compat import long
from pandas.core.arrays import (
PeriodArray, DatetimeArrayMixin as DatetimeArray,
TimedeltaArrayMixin as TimedeltaArray)
import pandas.util.testing as tm


Expand Down Expand Up @@ -174,44 +171,64 @@ def box_df_fail(request):
return request.param


@pytest.fixture(params=[(pd.Index, False),
(pd.Series, False),
@pytest.fixture(params=[pd.Index,
pd.Series,
(pd.DataFrame, False),
pytest.param((pd.DataFrame, True),
marks=pytest.mark.xfail(strict=True))],
(pd.DataFrame, True),
tm.to_array],
ids=id_func)
def box_transpose_fail(request):
def box_and_transpose(request):
"""
Fixture similar to `box` but testing both transpose cases for DataFrame,
with the tranpose=True case xfailed.
Like `box4`, but the DataFrame case with box transpose=True and
transpose=False
"""
# GH#23620
return request.param


@pytest.fixture(params=[pd.Index, pd.Series, pd.DataFrame, PeriodArray],
@pytest.fixture(params=[pd.Index,
pd.Series,
(pd.DataFrame, False),
pytest.param((pd.DataFrame, True),
marks=pytest.mark.xfail(strict=True)),
tm.to_array],
ids=id_func)
def box_with_period(request):
def box_transpose_fail(request):
"""
Like `box`, but specific to PeriodDtype for also testing PeriodArray
Like `box_and_transpose`, but xfailing the transposed DataFrame case
"""
# GH#23620
return request.param


@pytest.fixture(params=[pd.Index, pd.Series, pd.DataFrame, DatetimeArray],
ids=id_func)
def box_with_datetime(request):
"""
Like `box`, but specific to datetime64 for also testing DatetimeArray
"""
return request.param
# Construct tuples of the form (box, tz) over all our box classes and
# the timezones in tz_naive_fixture. We then xfail the cases where the box
# is a DataFrame-with-transpose and the timezone is not tz-naive (i.e. None)
boxes = [pd.Index,
pd.Series,
(pd.DataFrame, False),
(pd.DataFrame, True),
tm.to_array]

# copied from pandas.conftest tz_naive_fixture
TIMEZONES = [None, 'UTC', 'US/Eastern', 'Asia/Tokyo', 'dateutil/US/Pacific',
'dateutil/Asia/Singapore']

@pytest.fixture(params=[pd.Index, pd.Series, pd.DataFrame, TimedeltaArray],
ids=id_func)
def box_with_timedelta(request):
params = [(x, y) for x in boxes for y in TIMEZONES]
for n in range(len(params)):
tup = params[n]
if isinstance(tup[0], tuple) and tup[0][1] is True and tup[1] is not None:
# i.e. (DataFrame, True, tzinfo) excluding the no-tzinfo case
param = pytest.param(tup, marks=pytest.mark.xfail(strict=True))
params[n] = param


@pytest.fixture(params=params)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so my comment above was fairly general in nature. I don't think you need to test tz's other than [None, 'UTC'] or maybe use 'US/Eastern' rather than UTC to make sure that no times are shifted.

you actually just care if there IS a timezone or not.

def box_transpose_and_tz(request):
"""
Like `box`, but specific to timedelta64 for also testing TimedeltaArray
Fixture to test over Index, Series, DataFrame, and pandas Array, also
providing timezones, xfailing timezone-aware cases with transposed
DataFrame
"""
return request.param

Expand All @@ -224,3 +241,8 @@ def box_with_array(request):
classes
"""
return request.param


# aliases so we can use the same fixture twice in a test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these need self-obvious names

box_with_array2 = box_with_array
box_and_transpose2 = box_and_transpose
67 changes: 28 additions & 39 deletions pandas/tests/arithmetic/test_datetime64.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,14 @@ def test_dt64_ser_cmp_date_warning(self):
assert "a TypeError will be raised" in str(m[0].message)

@pytest.mark.skip(reason="GH#21359")
def test_dt64ser_cmp_date_invalid(self, box_with_datetime):
def test_dt64ser_cmp_date_invalid(self, box_and_transpose):
# GH#19800 datetime.date comparison raises to
# match DatetimeIndex/Timestamp. This also matches the behavior
# of stdlib datetime.datetime
box = box_with_datetime

ser = pd.date_range('20010101', periods=10)
date = ser.iloc[0].to_pydatetime().date()

ser = tm.box_expected(ser, box)
ser = tm.box_expected(ser, box_and_transpose)
assert not (ser == date).any()
assert (ser != date).all()
with pytest.raises(TypeError):
Expand Down Expand Up @@ -230,13 +228,12 @@ def test_timestamp_compare_series(self, left, right):
result = right_f(pd.Timestamp("nat"), s_nat)
tm.assert_series_equal(result, expected)

def test_dt64arr_timestamp_equality(self, box_with_datetime):
def test_dt64arr_timestamp_equality(self, box_with_array):
# GH#11034
box = box_with_datetime
xbox = box if box not in [pd.Index, DatetimeArray] else np.ndarray
xbox = box_with_array if box_with_array is not pd.Index else np.ndarray

ser = pd.Series([pd.Timestamp('2000-01-29 01:59:00'), 'NaT'])
ser = tm.box_expected(ser, box)
ser = tm.box_expected(ser, box_with_array)

result = ser != ser
expected = tm.box_expected([False, True], xbox)
Expand Down Expand Up @@ -1071,10 +1068,10 @@ def test_dti_add_sub_float(self, op, other):
with pytest.raises(TypeError):
op(dti, other)

def test_dti_add_timestamp_raises(self, box_with_datetime):
def test_dti_add_timestamp_raises(self, box_with_array):
# GH#22163 ensure DataFrame doesn't cast Timestamp to i8
idx = DatetimeIndex(['2011-01-01', '2011-01-02'])
idx = tm.box_expected(idx, box_with_datetime)
idx = tm.box_expected(idx, box_with_array)
msg = "cannot add"
with pytest.raises(TypeError, match=msg):
idx + Timestamp('2011-01-01')
Expand Down Expand Up @@ -1186,21 +1183,17 @@ def test_dti_add_intarray_no_freq(self, box):
# -------------------------------------------------------------
# Binary operations DatetimeIndex and timedelta-like

def test_dti_add_timedeltalike(self, tz_naive_fixture, two_hours,
box_with_datetime):
def test_dti_add_timedeltalike(self, two_hours, box_transpose_and_tz):
# GH#22005, GH#22163 check DataFrame doesn't raise TypeError
box = box_with_datetime

tz = tz_naive_fixture
box, tz = box_transpose_and_tz
rng = pd.date_range('2000-01-01', '2000-02-01', tz=tz)

# FIXME: calling with transpose=True raises ValueError
rng = tm.box_expected(rng, box, transpose=False)
rng = tm.box_expected(rng, box)

result = rng + two_hours
expected = pd.date_range('2000-01-01 02:00',
'2000-02-01 02:00', tz=tz)
expected = tm.box_expected(expected, box, transpose=False)
expected = tm.box_expected(expected, box)
tm.assert_equal(result, expected)

def test_dti_iadd_timedeltalike(self, tz_naive_fixture, two_hours):
Expand All @@ -1227,18 +1220,16 @@ def test_dti_isub_timedeltalike(self, tz_naive_fixture, two_hours):
rng -= two_hours
tm.assert_index_equal(rng, expected)

def test_dt64arr_add_sub_td64_nat(self, box, tz_naive_fixture):
def test_dt64arr_add_sub_td64_nat(self, box_transpose_and_tz):
# GH#23320 special handling for timedelta64("NaT")
tz = tz_naive_fixture
box, tz = box_transpose_and_tz

dti = pd.date_range("1994-04-01", periods=9, tz=tz, freq="QS")
other = np.timedelta64("NaT")
expected = pd.DatetimeIndex(["NaT"] * 9, tz=tz)

# FIXME: fails with transpose=True due to tz-aware DataFrame
# transpose bug
obj = tm.box_expected(dti, box, transpose=False)
expected = tm.box_expected(expected, box, transpose=False)
obj = tm.box_expected(dti, box)
expected = tm.box_expected(expected, box)

result = obj + other
tm.assert_equal(result, expected)
Expand Down Expand Up @@ -1473,13 +1464,13 @@ def test_sub_dti_dti(self):
tm.assert_index_equal(result, expected)

@pytest.mark.parametrize('dti_freq', [None, 'D'])
def test_dt64arr_add_sub_period(self, dti_freq, box_with_datetime):
def test_dt64arr_add_sub_period(self, dti_freq, box_and_transpose):
# GH#13078
# not supported, check TypeError
p = pd.Period('2011-01-01', freq='D')

idx = pd.DatetimeIndex(['2011-01-01', '2011-01-02'], freq=dti_freq)
idx = tm.box_expected(idx, box_with_datetime)
idx = tm.box_expected(idx, box_and_transpose)

with pytest.raises(TypeError):
idx + p
Expand All @@ -1493,13 +1484,13 @@ def test_dt64arr_add_sub_period(self, dti_freq, box_with_datetime):
@pytest.mark.parametrize('pi_freq', ['D', 'W', 'Q', 'H'])
@pytest.mark.parametrize('dti_freq', [None, 'D'])
def test_dti_add_sub_pi(self, dti_freq, pi_freq,
box_with_datetime, box_with_period):
box_with_array, box_with_array2):
# GH#20049 subtracting PeriodIndex should raise TypeError
dti = pd.DatetimeIndex(['2011-01-01', '2011-01-02'], freq=dti_freq)
pi = dti.to_period(pi_freq)

dtarr = tm.box_expected(dti, box_with_datetime)
parr = tm.box_expected(pi, box_with_period)
dtarr = tm.box_expected(dti, box_with_array)
parr = tm.box_expected(pi, box_with_array2)

with pytest.raises(TypeError):
dtarr + parr
Expand Down Expand Up @@ -1828,24 +1819,22 @@ def test_dti_with_offset_series(self, tz_naive_fixture, names):
res3 = dti - other
tm.assert_series_equal(res3, expected_sub)

def test_dti_add_offset_tzaware(self, tz_aware_fixture, box_with_datetime):
def test_dti_add_offset_tzaware(self, box_transpose_and_tz):
# GH#21610, GH#22163 ensure DataFrame doesn't return object-dtype
box = box_with_datetime
box, tz = box_transpose_and_tz

timezone = tz_aware_fixture
if timezone == 'US/Pacific':
dates = date_range('2012-11-01', periods=3, tz=timezone)
if tz == 'US/Pacific':
dates = date_range('2012-11-01', periods=3, tz=tz)
offset = dates + pd.offsets.Hour(5)
assert dates[0] + pd.offsets.Hour(5) == offset[0]

dates = date_range('2010-11-01 00:00',
periods=3, tz=timezone, freq='H')
periods=3, tz=tz, freq='H')
expected = DatetimeIndex(['2010-11-01 05:00', '2010-11-01 06:00',
'2010-11-01 07:00'], freq='H', tz=timezone)
'2010-11-01 07:00'], freq='H', tz=tz)

# FIXME: these raise ValueError with transpose=True
dates = tm.box_expected(dates, box, transpose=False)
expected = tm.box_expected(expected, box, transpose=False)
dates = tm.box_expected(dates, box)
expected = tm.box_expected(expected, box)

# TODO: parametrize over the scalar being added? radd? sub?
offset = dates + pd.offsets.Hour(5)
Expand Down
25 changes: 9 additions & 16 deletions pandas/tests/arithmetic/test_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,33 +556,28 @@ def test_pi_sub_isub_offset(self):
def test_pi_add_offset_n_gt1(self, box_transpose_fail):
# GH#23215
# add offset to PeriodIndex with freq.n > 1
box, transpose = box_transpose_fail

per = pd.Period('2016-01', freq='2M')
pi = pd.PeriodIndex([per])

expected = pd.PeriodIndex(['2016-03'], freq='2M')

pi = tm.box_expected(pi, box, transpose=transpose)
expected = tm.box_expected(expected, box, transpose=transpose)
pi = tm.box_expected(pi, box_transpose_fail)
expected = tm.box_expected(expected, box_transpose_fail)

result = pi + per.freq
tm.assert_equal(result, expected)

result = per.freq + pi
tm.assert_equal(result, expected)

def test_pi_add_offset_n_gt1_not_divisible(self, box_with_period):
def test_pi_add_offset_n_gt1_not_divisible(self, box_transpose_fail):
# GH#23215
# PeriodIndex with freq.n > 1 add offset with offset.n % freq.n != 0
box = box_with_period

pi = pd.PeriodIndex(['2016-01'], freq='2M')
expected = pd.PeriodIndex(['2016-04'], freq='2M')

# FIXME: with transposing these tests fail
pi = tm.box_expected(pi, box, transpose=False)
expected = tm.box_expected(expected, box, transpose=False)
pi = tm.box_expected(pi, box_transpose_fail)
expected = tm.box_expected(expected, box_transpose_fail)

result = pi + to_offset('3M')
tm.assert_equal(result, expected)
Expand Down Expand Up @@ -798,14 +793,12 @@ def test_pi_add_sub_timedeltalike_freq_mismatch_monthly(self,

def test_parr_add_sub_td64_nat(self, box_transpose_fail):
# GH#23320 special handling for timedelta64("NaT")
box, transpose = box_transpose_fail

pi = pd.period_range("1994-04-01", periods=9, freq="19D")
other = np.timedelta64("NaT")
expected = pd.PeriodIndex(["NaT"] * 9, freq="19D")

obj = tm.box_expected(pi, box, transpose=transpose)
expected = tm.box_expected(expected, box, transpose=transpose)
obj = tm.box_expected(pi, box_transpose_fail)
expected = tm.box_expected(expected, box_transpose_fail)

result = obj + other
tm.assert_equal(result, expected)
Expand Down Expand Up @@ -898,10 +891,10 @@ def test_pi_ops(self):
tm.assert_index_equal(result, exp)

@pytest.mark.parametrize('ng', ["str", 1.5])
def test_pi_ops_errors(self, ng, box_with_period):
def test_pi_ops_errors(self, ng, box_with_array):
idx = PeriodIndex(['2011-01', '2011-02', '2011-03', '2011-04'],
freq='M', name='idx')
obj = tm.box_expected(idx, box_with_period)
obj = tm.box_expected(idx, box_with_array)

msg = r"unsupported operand type\(s\)"
with pytest.raises(TypeError, match=msg):
Expand Down
11 changes: 5 additions & 6 deletions pandas/tests/arithmetic/test_timedelta64.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,19 +413,18 @@ def test_td64arr_sub_timestamp_raises(self, box):
with pytest.raises(TypeError, match=msg):
idx - Timestamp('2011-01-01')

def test_td64arr_add_timestamp(self, box, tz_naive_fixture):
def test_td64arr_add_timestamp(self, box_transpose_and_tz):
# GH#23215
# TODO: parametrize over scalar datetime types?
tz = tz_naive_fixture
box, tz = box_transpose_and_tz

other = Timestamp('2011-01-01', tz=tz)

idx = TimedeltaIndex(['1 day', '2 day'])
expected = DatetimeIndex(['2011-01-02', '2011-01-03'], tz=tz)

# FIXME: fails with transpose=True because of tz-aware DataFrame
# transpose bug
idx = tm.box_expected(idx, box, transpose=False)
expected = tm.box_expected(expected, box, transpose=False)
idx = tm.box_expected(idx, box)
expected = tm.box_expected(expected, box)

result = idx + other
tm.assert_equal(result, expected)
Expand Down
6 changes: 6 additions & 0 deletions pandas/util/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,12 @@ def box_expected(expected, box_cls, transpose=True):
# not a single-column, in order to operate against non-DataFrame
# vectors of the same length.
expected = expected.T
elif isinstance(box_cls, tuple):
# compat for passing transpose in the same fixture as box without
# unpacking the tuple in every test
assert len(box_cls) == 2
box_cls, transpose = box_cls
return box_expected(expected, box_cls, transpose=transpose)
elif box_cls is PeriodArray:
# the PeriodArray constructor is not as flexible as period_array
expected = period_array(expected)
Expand Down