From 29d216e974086247e7d3a3da143b6170651b7af1 Mon Sep 17 00:00:00 2001 From: Vyas Ramasubramani Date: Sun, 6 Sep 2026 11:13:11 -0700 Subject: [PATCH 1/3] TEST: Extract duplicate str.cat index checks --- python/cudf/cudf/tests/series/accessors/test_str.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/python/cudf/cudf/tests/series/accessors/test_str.py b/python/cudf/cudf/tests/series/accessors/test_str.py index 1d3d1daada1f..90f293a7408f 100644 --- a/python/cudf/cudf/tests/series/accessors/test_str.py +++ b/python/cudf/cudf/tests/series/accessors/test_str.py @@ -2823,9 +2823,13 @@ def test_string_cat(ps_gs, others, sep, na_rep, index, request): expect.index = expect.index.astype(got.index.dtype) assert_eq(expect, got) + +@pytest.mark.parametrize("sep", [None, "", " ", "|", ",", "|||"]) +@pytest.mark.parametrize("na_rep", [None, "", "null", "a"]) +def test_string_cat_index_others(data, sep, na_rep): index = ["1", "2", "3", "4", "5"] - ps.index = index - gs.index = index + ps = pd.Series(data, index=index, dtype="str", name="nice name") + gs = cudf.Series(data, index=index, dtype="str", name="nice name") expect = ps.str.cat(others=ps.index, sep=sep, na_rep=na_rep) got = gs.str.cat(others=gs.index, sep=sep, na_rep=na_rep) From 75a4e68eccae78dc1b4133f5f9799a0f25dfd15e Mon Sep 17 00:00:00 2001 From: Vyas Ramasubramani Date: Sun, 6 Sep 2026 12:24:03 -0700 Subject: [PATCH 2/3] TEST: Split str.cat coverage by behavior --- .../cudf/tests/series/accessors/test_str.py | 335 ++++++++++++------ 1 file changed, 223 insertions(+), 112 deletions(-) diff --git a/python/cudf/cudf/tests/series/accessors/test_str.py b/python/cudf/cudf/tests/series/accessors/test_str.py index 90f293a7408f..d9465d5abfcb 100644 --- a/python/cudf/cudf/tests/series/accessors/test_str.py +++ b/python/cudf/cudf/tests/series/accessors/test_str.py @@ -2692,136 +2692,247 @@ def test_string_index_duplicate_str_cat(data, others, sep, na_rep, name): ) +def _assert_string_cat(data, others, sep, na_rep, index=None): + ps = pd.Series(data, index=index, dtype="str", name="nice name") + gs = cudf.Series(data, index=index, dtype="str", name="nice name") + is_any_others_ndarray = isinstance(others, (list, tuple)) and any( + isinstance(item, np.ndarray) for item in others + ) + + expect = ps.str.cat(others=others, sep=sep, na_rep=na_rep) + got = gs.str.cat( + others=_cat_convert_seq_to_cudf(others), sep=sep, na_rep=na_rep + ) + if is_any_others_ndarray: + # pandas returns Index[object] which cuDF doesn't support + expect.index = expect.index.astype(got.index.dtype) + assert_eq(expect, got) + + @pytest.mark.parametrize( - "others", + "data, sep, na_rep", [ - None, - ["f", "g", "h", "i", "j"], - ("f", "g", "h", "i", "j"), - pd.Series(["f", "g", "h", "i", "j"]), - pd.Series(["AbC", "de", "FGHI", "j", "kLm"]), - pd.Index(["f", "g", "h", "i", "j"]), - pd.Index(["AbC", "de", "FGHI", "j", "kLm"]), - ( - np.array(["f", "g", "h", "i", "j"]), - np.array(["f", "g", "h", "i", "j"]), + pytest.param(["AbC", "de", "FGHI", "j", "kLm"], None, None), + pytest.param(["AbC", "de", "FGHI", "j", "kLm"], "", None), + pytest.param(["AbC", "de", "FGHI", "j", "kLm"], "|", None), + pytest.param(["AbC", "de", "FGHI", "j", "kLm"], "|||", None), + pytest.param(["nOPq", None, "RsT", None, "uVw"], "|", None), + pytest.param(["nOPq", None, "RsT", None, "uVw"], "|", ""), + pytest.param(["nOPq", None, "RsT", None, "uVw"], "|", "null"), + pytest.param([None, None, None, None, None], "|", "null"), + ], +) +def test_string_cat_join(data, sep, na_rep): + _assert_string_cat(data, None, sep, na_rep) + + +@pytest.mark.parametrize( + "data, others, sep, na_rep", + [ + pytest.param( + ["nOPq", None, "RsT", None, "uVw"], + ["f", "g", "h", "i", "j"], + None, + None, ), - [ - np.array(["f", "g", "h", "i", "j"]), - np.array(["f", "g", "h", "i", "j"]), - ], - [ - pd.Series(["f", "g", "h", "i", "j"]), - pd.Series(["f", "g", "h", "i", "j"]), - ], - ( - pd.Series(["f", "g", "h", "i", "j"]), - pd.Series(["f", "g", "h", "i", "j"]), + pytest.param( + ["nOPq", None, "RsT", None, "uVw"], + ["f", "g", "h", "i", "j"], + "|", + "", ), - [ - pd.Series(["f", "g", "h", "i", "j"]), - np.array(["f", "g", "h", "i", "j"]), - ], - ( + pytest.param( + ["nOPq", None, "RsT", None, "uVw"], pd.Series(["f", "g", "h", "i", "j"]), - np.array(["f", "g", "h", "i", "j"]), + "|", + "null", ), - ( - pd.Series(["f", "g", "h", "i", "j"]), - np.array(["f", "a", "b", "f", "a"]), - pd.Series(["f", "g", "h", "i", "j"]), - np.array(["f", "a", "b", "f", "a"]), - np.array(["f", "a", "b", "f", "a"]), - pd.Index(["1", "2", "3", "4", "5"]), - np.array(["f", "a", "b", "f", "a"]), - pd.Index(["f", "g", "h", "i", "j"]), + pytest.param( + [None, None, None, None, None], + ["f", "g", "h", "i", "j"], + "|", + None, ), - [ - pd.Index(["f", "g", "h", "i", "j"]), - np.array(["f", "a", "b", "f", "a"]), - pd.Series(["f", "g", "h", "i", "j"]), - np.array(["f", "a", "b", "f", "a"]), - np.array(["f", "a", "b", "f", "a"]), - pd.Index(["f", "g", "h", "i", "j"]), - np.array(["f", "a", "b", "f", "a"]), + pytest.param( + [None, None, None, None, None], pd.Index(["f", "g", "h", "i", "j"]), - ], - [ - pd.Series(["hello", "world", "abc", "xyz", "pqr"]), - pd.Series(["abc", "xyz", "hello", "pqr", "world"]), - ], - [ - pd.Series( - ["hello", "world", "abc", "xyz", "pqr"], - index=[10, 11, 12, 13, 14], - ), - pd.Series( - ["abc", "xyz", "hello", "pqr", "world"], - index=[10, 15, 11, 13, 14], + "|", + "null", + ), + ], +) +def test_string_cat_elementwise_nulls(data, others, sep, na_rep): + _assert_string_cat(data, others, sep, na_rep) + + +@pytest.mark.parametrize( + "others", + [ + pytest.param(["f", "g", "h", "i", "j"], id="list"), + pytest.param(("f", "g", "h", "i", "j"), id="tuple"), + pytest.param(pd.Series(["f", "g", "h", "i", "j"]), id="series"), + pytest.param(pd.Index(["f", "g", "h", "i", "j"]), id="index"), + pytest.param( + ( + np.array(["f", "g", "h", "i", "j"]), + np.array(["f", "g", "h", "i", "j"]), ), - ], - [ - pd.Series( - ["hello", "world", "abc", "xyz", "pqr"], - index=["10", "11", "12", "13", "14"], + id="tuple-of-ndarrays", + ), + pytest.param( + [ + np.array(["f", "g", "h", "i", "j"]), + np.array(["f", "g", "h", "i", "j"]), + ], + id="list-of-ndarrays", + ), + pytest.param( + [ + pd.Series(["f", "g", "h", "i", "j"]), + pd.Series(["f", "g", "h", "i", "j"]), + ], + id="list-of-series", + ), + pytest.param( + ( + pd.Series(["f", "g", "h", "i", "j"]), + pd.Series(["f", "g", "h", "i", "j"]), ), - pd.Series( - ["abc", "xyz", "hello", "pqr", "world"], - index=["10", "11", "12", "13", "14"], + id="tuple-of-series", + ), + pytest.param( + [ + pd.Series(["f", "g", "h", "i", "j"]), + np.array(["f", "g", "h", "i", "j"]), + ], + id="list-of-series-and-ndarray", + ), + pytest.param( + ( + pd.Series(["f", "g", "h", "i", "j"]), + np.array(["f", "g", "h", "i", "j"]), ), - ], - [ - pd.Series( - ["hello", "world", "abc", "xyz", "pqr"], - index=["10", "11", "12", "13", "14"], + id="tuple-of-series-and-ndarray", + ), + pytest.param( + ( + pd.Series(["f", "g", "h", "i", "j"]), + np.array(["f", "a", "b", "f", "a"]), + pd.Series(["f", "g", "h", "i", "j"]), + np.array(["f", "a", "b", "f", "a"]), + np.array(["f", "a", "b", "f", "a"]), + pd.Index(["1", "2", "3", "4", "5"]), + np.array(["f", "a", "b", "f", "a"]), + pd.Index(["f", "g", "h", "i", "j"]), ), - pd.Series( - ["abc", "xyz", "hello", "pqr", "world"], - index=["10", "15", "11", "13", "14"], + id="heterogeneous-tuple", + ), + pytest.param( + [ + pd.Index(["f", "g", "h", "i", "j"]), + np.array(["f", "a", "b", "f", "a"]), + pd.Series(["f", "g", "h", "i", "j"]), + np.array(["f", "a", "b", "f", "a"]), + np.array(["f", "a", "b", "f", "a"]), + pd.Index(["f", "g", "h", "i", "j"]), + np.array(["f", "a", "b", "f", "a"]), + pd.Index(["f", "g", "h", "i", "j"]), + ], + id="heterogeneous-list", + ), + ], +) +def test_string_cat_input_forms(others): + _assert_string_cat(["AbC", "de", "FGHI", "j", "kLm"], others, "|", None) + + +@pytest.mark.parametrize( + "index, others", + [ + pytest.param( + [10, 11, 12, 13, 14], + pd.Series(["f", "g", "h", "i", "j"]), + id="series-reindex", + ), + pytest.param( + None, + [ + pd.Series(["hello", "world", "abc", "xyz", "pqr"]), + pd.Series(["abc", "xyz", "hello", "pqr", "world"]), + ], + id="multiple-series-default-index", + ), + pytest.param( + None, + [ + pd.Series( + ["hello", "world", "abc", "xyz", "pqr"], + index=[10, 11, 12, 13, 14], + ), + pd.Series( + ["abc", "xyz", "hello", "pqr", "world"], + index=[10, 15, 11, 13, 14], + ), + ], + id="multiple-series-partial-integer-index", + ), + pytest.param( + None, + [ + pd.Series( + ["hello", "world", "abc", "xyz", "pqr"], + index=["10", "11", "12", "13", "14"], + ), + pd.Series( + ["abc", "xyz", "hello", "pqr", "world"], + index=["10", "11", "12", "13", "14"], + ), + ], + marks=pytest.mark.xfail( + reason="https://github.com/NVIDIA/cudf/issues/21123" ), - ], - [ - pd.Series( - ["hello", "world", "abc", "xyz", "pqr"], - index=["1", "2", "3", "4", "5"], + id="multiple-series-matching-string-index", + ), + pytest.param( + None, + [ + pd.Series( + ["hello", "world", "abc", "xyz", "pqr"], + index=["10", "11", "12", "13", "14"], + ), + pd.Series( + ["abc", "xyz", "hello", "pqr", "world"], + index=["10", "15", "11", "13", "14"], + ), + ], + marks=pytest.mark.xfail( + reason="https://github.com/NVIDIA/cudf/issues/21123" ), - pd.Series( - ["abc", "xyz", "hello", "pqr", "world"], - index=["10", "11", "12", "13", "14"], + id="multiple-series-partial-string-index", + ), + pytest.param( + None, + [ + pd.Series( + ["hello", "world", "abc", "xyz", "pqr"], + index=["1", "2", "3", "4", "5"], + ), + pd.Series( + ["abc", "xyz", "hello", "pqr", "world"], + index=["10", "11", "12", "13", "14"], + ), + ], + marks=pytest.mark.xfail( + reason="https://github.com/NVIDIA/cudf/issues/21123" ), - ], + id="multiple-series-disjoint-string-index", + ), ], ) -@pytest.mark.parametrize("sep", [None, "", " ", "|", ",", "|||"]) -@pytest.mark.parametrize("na_rep", [None, "", "null", "a"]) -def test_string_cat(ps_gs, others, sep, na_rep, index, request): - # https://github.com/pandas-dev/pandas/issues/63371 - ps, gs = ps_gs - is_any_others_ndarray = isinstance(others, (list, tuple)) and any( - isinstance(item, np.ndarray) for item in others +def test_string_cat_series_alignment(index, others): + _assert_string_cat( + ["AbC", "de", "FGHI", "j", "kLm"], others, "|", None, index ) - is_any_others_series_with_string_index = isinstance( - others, (list, tuple) - ) and any( - isinstance(item, pd.Series) - and isinstance(item.index.dtype, pd.StringDtype) - for item in others - ) - request.applymarker( - pytest.mark.xfail( - is_any_others_series_with_string_index, - reason="https://github.com/NVIDIA/cudf/issues/21123", - ) - ) - pd_others = others - gd_others = _cat_convert_seq_to_cudf(others) - - expect = ps.str.cat(others=pd_others, sep=sep, na_rep=na_rep) - got = gs.str.cat(others=gd_others, sep=sep, na_rep=na_rep) - if is_any_others_ndarray: - # pandas returns Index[object] which cuDF doesn't support - expect.index = expect.index.astype(got.index.dtype) - assert_eq(expect, got) @pytest.mark.parametrize("sep", [None, "", " ", "|", ",", "|||"]) From 00e944dce68000a7b4f9ab6883e8238181818cc4 Mon Sep 17 00:00:00 2001 From: Vyas Ramasubramani Date: Tue, 8 Sep 2026 09:09:49 -0700 Subject: [PATCH 3/3] TEST: Keep str cat index expectation independent --- python/cudf/cudf/tests/series/accessors/test_str.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cudf/cudf/tests/series/accessors/test_str.py b/python/cudf/cudf/tests/series/accessors/test_str.py index d9465d5abfcb..b1a1166ef7e6 100644 --- a/python/cudf/cudf/tests/series/accessors/test_str.py +++ b/python/cudf/cudf/tests/series/accessors/test_str.py @@ -2705,7 +2705,7 @@ def _assert_string_cat(data, others, sep, na_rep, index=None): ) if is_any_others_ndarray: # pandas returns Index[object] which cuDF doesn't support - expect.index = expect.index.astype(got.index.dtype) + expect.index = expect.index.astype(ps.index.dtype) assert_eq(expect, got)