From 787d6f3355300551cef495dfb0b44df5327d0b45 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 2 Feb 2021 05:35:17 -0800 Subject: [PATCH] TST: trim fixture to avoid xfails/skips (#39502) --- pandas/conftest.py | 36 +++++++++++-- pandas/tests/indexes/common.py | 2 +- pandas/tests/indexes/test_base.py | 5 +- pandas/tests/indexes/test_common.py | 84 ++++++++++++----------------- pandas/tests/indexes/test_setops.py | 64 ++++++++++++---------- 5 files changed, 105 insertions(+), 86 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index 0734cf12cce0d..829ac64884dac 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -480,13 +480,41 @@ def index(request): index_fixture2 = index -@pytest.fixture(params=indices_dict.keys()) +@pytest.fixture( + params=[ + key for key in indices_dict if not isinstance(indices_dict[key], MultiIndex) + ] +) +def index_flat(request): + """ + index fixture, but excluding MultiIndex cases. + """ + key = request.param + return indices_dict[key].copy() + + +# Alias so we can test with cartesian product of index_flat +index_flat2 = index_flat + + +@pytest.fixture( + params=[ + key + for key in indices_dict + if key not in ["int", "uint", "range", "empty", "repeats"] + and not isinstance(indices_dict[key], MultiIndex) + ] +) def index_with_missing(request): """ - Fixture for indices with missing values + Fixture for indices with missing values. + + Integer-dtype and empty cases are excluded because they cannot hold missing + values. + + MultiIndex is excluded because isna() is not defined for MultiIndex. """ - if request.param in ["int", "uint", "range", "empty", "repeats"]: - pytest.skip("missing values not supported") + # GH 35538. Use deep copy to avoid illusive bug on np-dev # Azure pipeline that writes into indices_dict despite copy ind = indices_dict[request.param].copy(deep=True) diff --git a/pandas/tests/indexes/common.py b/pandas/tests/indexes/common.py index b756ad6051a86..c70401ac14e7d 100644 --- a/pandas/tests/indexes/common.py +++ b/pandas/tests/indexes/common.py @@ -630,7 +630,7 @@ def test_map(self): def test_map_dictlike(self, mapper): index = self.create_index() - if isinstance(index, (pd.CategoricalIndex, pd.IntervalIndex)): + if isinstance(index, pd.CategoricalIndex): pytest.skip(f"skipping tests for {type(index)}") identity = mapper(index.values, index) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 092b1c447eb0d..42769e5daf059 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -878,14 +878,11 @@ def test_difference_name_preservation(self, index, second_name, expected, sort): assert result.name == expected def test_difference_empty_arg(self, index, sort): - if isinstance(index, MultiIndex): - pytest.skip("Not applicable") first = index[5:20] first.name = "name" result = first.difference([], sort) - assert tm.equalContents(result, first) - assert result.name == first.name + tm.assert_index_equal(result, first) @pytest.mark.parametrize("index", ["string"], indirect=True) def test_difference_identity(self, index, sort): diff --git a/pandas/tests/indexes/test_common.py b/pandas/tests/indexes/test_common.py index 931f01cecab0b..d622ea359bc53 100644 --- a/pandas/tests/indexes/test_common.py +++ b/pandas/tests/indexes/test_common.py @@ -52,11 +52,9 @@ def test_droplevel(self, index): ): index.droplevel(level) - def test_constructor_non_hashable_name(self, index): + def test_constructor_non_hashable_name(self, index_flat): # GH 20527 - - if isinstance(index, MultiIndex): - pytest.skip("multiindex handled in test_multi.py") + index = index_flat message = "Index.name must be a hashable type" renamed = [["1"]] @@ -69,27 +67,23 @@ def test_constructor_non_hashable_name(self, index): with pytest.raises(TypeError, match=message): index.set_names(names=renamed) - def test_constructor_unwraps_index(self, index): - if isinstance(index, pd.MultiIndex): - raise pytest.skip("MultiIndex has no ._data") - a = index + def test_constructor_unwraps_index(self, index_flat): + a = index_flat b = type(a)(a) tm.assert_equal(a._data, b._data) - def test_to_flat_index(self, index): + def test_to_flat_index(self, index_flat): # 22866 - if isinstance(index, MultiIndex): - pytest.skip("Separate expectation for MultiIndex") + index = index_flat result = index.to_flat_index() tm.assert_index_equal(result, index) - def test_set_name_methods(self, index): + def test_set_name_methods(self, index_flat): + # MultiIndex tested separately + index = index_flat new_name = "This is the new name for this index" - # don't tests a MultiIndex here (as its tested separated) - if isinstance(index, MultiIndex): - pytest.skip("Skip check for MultiIndex") original_name = index.name new_ind = index.set_names([new_name]) assert new_ind.name == new_name @@ -113,11 +107,10 @@ def test_set_name_methods(self, index): assert index.name == name assert index.names == [name] - def test_copy_and_deepcopy(self, index): + def test_copy_and_deepcopy(self, index_flat): from copy import copy, deepcopy - if isinstance(index, MultiIndex): - pytest.skip("Skip check for MultiIndex") + index = index_flat for func in (copy, deepcopy): idx_copy = func(index) @@ -127,10 +120,9 @@ def test_copy_and_deepcopy(self, index): new_copy = index.copy(deep=True, name="banana") assert new_copy.name == "banana" - def test_unique(self, index): + def test_unique(self, index_flat): # don't test a MultiIndex here (as its tested separated) - if isinstance(index, MultiIndex): - pytest.skip("Skip check for MultiIndex") + index = index_flat # GH 17896 expected = index.drop_duplicates() @@ -149,9 +141,10 @@ def test_unique(self, index): with pytest.raises(KeyError, match=msg): index.unique(level="wrong") - def test_get_unique_index(self, index): + def test_get_unique_index(self, index_flat): # MultiIndex tested separately - if not len(index) or isinstance(index, MultiIndex): + index = index_flat + if not len(index): pytest.skip("Skip check for empty Index and MultiIndex") idx = index[[0] * 5] @@ -200,11 +193,12 @@ def test_get_unique_index(self, index): result = i._get_unique_index(dropna=dropna) tm.assert_index_equal(result, expected) - def test_searchsorted_monotonic(self, index): + def test_searchsorted_monotonic(self, index_flat): # GH17271 + index = index_flat # not implemented for tuple searches in MultiIndex # or Intervals searches in IntervalIndex - if isinstance(index, (MultiIndex, pd.IntervalIndex)): + if isinstance(index, pd.IntervalIndex): pytest.skip("Skip check for MultiIndex/IntervalIndex") # nothing to test if the index is empty @@ -245,9 +239,9 @@ def test_searchsorted_monotonic(self, index): with pytest.raises(ValueError, match=msg): index._searchsorted_monotonic(value, side="left") - def test_drop_duplicates(self, index, keep): - if isinstance(index, MultiIndex): - pytest.skip("MultiIndex is tested separately") + def test_drop_duplicates(self, index_flat, keep): + # MultiIndex is tested separately + index = index_flat if isinstance(index, RangeIndex): pytest.skip( "RangeIndex is tested in test_drop_duplicates_no_duplicates " @@ -279,9 +273,9 @@ def test_drop_duplicates(self, index, keep): expected_dropped = holder(pd.Series(idx).drop_duplicates(keep=keep)) tm.assert_index_equal(idx.drop_duplicates(keep=keep), expected_dropped) - def test_drop_duplicates_no_duplicates(self, index): - if isinstance(index, MultiIndex): - pytest.skip("MultiIndex is tested separately") + def test_drop_duplicates_no_duplicates(self, index_flat): + # MultiIndex is tested separately + index = index_flat # make unique index if isinstance(index, RangeIndex): @@ -305,9 +299,12 @@ def test_drop_duplicates_inplace(self, index): with pytest.raises(TypeError, match=msg): index.drop_duplicates(inplace=True) - def test_has_duplicates(self, index): + def test_has_duplicates(self, index_flat): + # MultiIndex tested separately in: + # tests/indexes/multi/test_unique_and_duplicates. + index = index_flat holder = type(index) - if not len(index) or isinstance(index, (MultiIndex, RangeIndex)): + if not len(index) or isinstance(index, RangeIndex): # MultiIndex tested separately in: # tests/indexes/multi/test_unique_and_duplicates. # RangeIndex is unique by definition. @@ -363,29 +360,18 @@ def test_asi8_deprecation(self, index): @pytest.mark.parametrize("na_position", [None, "middle"]) -def test_sort_values_invalid_na_position(request, index_with_missing, na_position): - if isinstance(index_with_missing, MultiIndex): - request.node.add_marker( - pytest.mark.xfail( - reason="missing value sorting order not defined for index type" - ) - ) +def test_sort_values_invalid_na_position(index_with_missing, na_position): - if na_position not in ["first", "last"]: - with pytest.raises(ValueError, match=f"invalid na_position: {na_position}"): - index_with_missing.sort_values(na_position=na_position) + with pytest.raises(ValueError, match=f"invalid na_position: {na_position}"): + index_with_missing.sort_values(na_position=na_position) @pytest.mark.parametrize("na_position", ["first", "last"]) -def test_sort_values_with_missing(request, index_with_missing, na_position): +def test_sort_values_with_missing(index_with_missing, na_position): # GH 35584. Test that sort_values works with missing values, # sort non-missing and place missing according to na_position - if isinstance(index_with_missing, MultiIndex): - request.node.add_marker( - pytest.mark.xfail(reason="missing value sorting order not implemented") - ) - elif isinstance(index_with_missing, CategoricalIndex): + if isinstance(index_with_missing, CategoricalIndex): pytest.skip("missing value sorting order not well-defined") missing_count = np.sum(index_with_missing.isna()) diff --git a/pandas/tests/indexes/test_setops.py b/pandas/tests/indexes/test_setops.py index f2a33df71e8e3..746b6d6fb6e2a 100644 --- a/pandas/tests/indexes/test_setops.py +++ b/pandas/tests/indexes/test_setops.py @@ -38,33 +38,39 @@ def test_union_same_types(index): assert idx1.union(idx2).dtype == idx1.dtype -def test_union_different_types(request, index, index_fixture2): +def test_union_different_types(index_flat, index_flat2): # This test only considers combinations of indices # GH 23525 - idx1, idx2 = index, index_fixture2 - type_pair = tuple(sorted([type(idx1), type(idx2)], key=lambda x: str(x))) - if type_pair in COMPATIBLE_INCONSISTENT_PAIRS: - request.node.add_marker( - pytest.mark.xfail(reason="This test only considers non compatible indexes.") - ) - - if any(isinstance(idx, pd.MultiIndex) for idx in (idx1, idx2)): - pytest.skip("This test doesn't consider multiindixes.") + idx1 = index_flat + idx2 = index_flat2 - if is_dtype_equal(idx1.dtype, idx2.dtype): - pytest.skip("This test only considers non matching dtypes.") - - # A union with a CategoricalIndex (even as dtype('O')) and a - # non-CategoricalIndex can only be made if both indices are monotonic. - # This is true before this PR as well. + type_pair = tuple(sorted([type(idx1), type(idx2)], key=lambda x: str(x))) # Union with a non-unique, non-monotonic index raises error # This applies to the boolean index idx1 = idx1.sort_values() idx2 = idx2.sort_values() - assert idx1.union(idx2).dtype == np.dtype("O") - assert idx2.union(idx1).dtype == np.dtype("O") + res1 = idx1.union(idx2) + res2 = idx2.union(idx1) + + if is_dtype_equal(idx1.dtype, idx2.dtype): + assert res1.dtype == idx1.dtype + assert res2.dtype == idx1.dtype + + elif type_pair not in COMPATIBLE_INCONSISTENT_PAIRS: + # A union with a CategoricalIndex (even as dtype('O')) and a + # non-CategoricalIndex can only be made if both indices are monotonic. + # This is true before this PR as well. + assert res1.dtype == np.dtype("O") + assert res2.dtype == np.dtype("O") + + elif idx1.dtype.kind in ["f", "i", "u"] and idx2.dtype.kind in ["f", "i", "u"]: + assert res1.dtype == np.dtype("f8") + assert res2.dtype == np.dtype("f8") + + else: + raise NotImplementedError @pytest.mark.parametrize("idx_fact1,idx_fact2", COMPATIBLE_INCONSISTENT_PAIRS.values()) @@ -275,12 +281,12 @@ def test_symmetric_difference(self, index): (None, None, None), ], ) - def test_corner_union(self, index, fname, sname, expected_name): + def test_corner_union(self, index_flat, fname, sname, expected_name): # GH#9943, GH#9862 # Test unions with various name combinations # Do not test MultiIndex or repeats - - if isinstance(index, MultiIndex) or not index.is_unique: + index = index_flat + if not index.is_unique: pytest.skip("Not for MultiIndex or repeated indices") # Test copy.union(copy) @@ -321,8 +327,9 @@ def test_corner_union(self, index, fname, sname, expected_name): (None, None, None), ], ) - def test_union_unequal(self, index, fname, sname, expected_name): - if isinstance(index, MultiIndex) or not index.is_unique: + def test_union_unequal(self, index_flat, fname, sname, expected_name): + index = index_flat + if not index.is_unique: pytest.skip("Not for MultiIndex or repeated indices") # test copy.union(subset) - need sort for unicode and string @@ -342,11 +349,11 @@ def test_union_unequal(self, index, fname, sname, expected_name): (None, None, None), ], ) - def test_corner_intersect(self, index, fname, sname, expected_name): + def test_corner_intersect(self, index_flat, fname, sname, expected_name): # GH#35847 # Test intersections with various name combinations - - if isinstance(index, MultiIndex) or not index.is_unique: + index = index_flat + if not index.is_unique: pytest.skip("Not for MultiIndex or repeated indices") # Test copy.intersection(copy) @@ -387,8 +394,9 @@ def test_corner_intersect(self, index, fname, sname, expected_name): (None, None, None), ], ) - def test_intersect_unequal(self, index, fname, sname, expected_name): - if isinstance(index, MultiIndex) or not index.is_unique: + def test_intersect_unequal(self, index_flat, fname, sname, expected_name): + index = index_flat + if not index.is_unique: pytest.skip("Not for MultiIndex or repeated indices") # test copy.intersection(subset) - need sort for unicode and string