@@ -144,6 +144,35 @@ def test_create_with_na(dtype):
144144 assert arr [1 ] is dtype .na_object
145145
146146
147+ def test_create_with_failing_na_comparison ():
148+ # An error raised while re-creating an array-owned descriptor used to
149+ # crash with a NULL dereference in stringdtype_finalize_descr instead
150+ # of propagating.
151+ class Flaky :
152+ def __init__ (self ):
153+ self .calls = 0
154+
155+ def __eq__ (self , other ):
156+ return self is other
157+
158+ def __ne__ (self , other ):
159+ self .calls += 1
160+ if self .calls > 1 :
161+ raise RuntimeError ("boom" )
162+ return False
163+
164+ def __hash__ (self ):
165+ return 0
166+
167+ dt = StringDType (na_object = Flaky ())
168+ # the first array takes ownership of the descriptor
169+ np .array (["x" ], dtype = dt )
170+ # the second array creation re-creates the descriptor, re-running the
171+ # na object classification, which fails
172+ with pytest .raises (RuntimeError , match = "boom" ):
173+ np .array (["y" ], dtype = dt )
174+
175+
147176@pytest .mark .parametrize ("i" , list (range (5 )))
148177def test_set_replace_na (i ):
149178 # Test strings of various lengths can be set to NaN and then replaced.
@@ -168,6 +197,18 @@ def test_null_roundtripping():
168197 assert data [1 ] == arr [1 ]
169198
170199
200+ @pytest .mark .parametrize ("coerce" , [True , False ])
201+ def test_np_str_trailing_nul_preserved (coerce ):
202+ dtype = StringDType (coerce = coerce )
203+ value = np .str_ ("q\x00 " )
204+ arr = np .empty (1 , dtype = dtype )
205+ arr [0 ] = value
206+ assert arr [0 ] == "q\x00 "
207+ arr [0 :1 ] = [value ]
208+ assert arr [0 ] == "q\x00 "
209+ assert np .array ([value ], dtype = dtype )[0 ] == "q\x00 "
210+
211+
171212@pytest .mark .parametrize (
172213 "op, pyop" ,
173214 [
@@ -311,6 +352,17 @@ def test_self_casts(dtype, dtype2, strings):
311352 assert_array_equal (arr [:- 1 ], newarr [:- 1 ])
312353
313354
355+ def test_cast_method_names ():
356+ get_castingimpl = np ._core ._multiarray_umath ._get_castingimpl
357+ string_DT = type (StringDType ())
358+ for other_DT , name in [(np .dtypes .BoolDType , "bool" ),
359+ (np .dtypes .Float64DType , "double" )]:
360+ to_string = get_castingimpl (other_DT , string_DT )
361+ assert f"cast_{ name } _to_StringDType" in repr (to_string )
362+ from_string = get_castingimpl (string_DT , other_DT )
363+ assert f"cast_StringDType_to_{ name } " in repr (from_string )
364+
365+
314366@pytest .mark .parametrize (
315367 ("strings" ),
316368 [
@@ -911,6 +963,15 @@ def test_string_to_bytes_invalid_ascii_error():
911963 )
912964
913965
966+ def test_void_to_string_invalid_utf8_error ():
967+ # invalid UTF-8 in a void buffer used to surface as a confusing
968+ # MemoryError because the error return of utf8_buffer_size was
969+ # stored in an unsigned variable
970+ varr = np .array ([b"\xff \xff \xff \xff " ], dtype = "V4" )
971+ with pytest .raises (TypeError , match = "Invalid UTF-8" ):
972+ varr .astype (StringDType ())
973+
974+
914975@pytest .mark .parametrize (
915976 "typename" ,
916977 [
@@ -1786,6 +1847,17 @@ def test_unset_na_coercion():
17861847 arr == op
17871848
17881849
1850+ def test_coerce_promotion_commutative ():
1851+ # promoting a coerce=False instance with a coerce=True instance
1852+ # used to depend on the argument order
1853+ dt = StringDType ()
1854+ dt_no_coerce = StringDType (coerce = False )
1855+ assert np .promote_types (dt , dt_no_coerce ) == dt_no_coerce
1856+ assert np .promote_types (dt_no_coerce , dt ) == dt_no_coerce
1857+ assert np .promote_types (dt , dt ) == dt
1858+ assert np .promote_types (dt_no_coerce , dt_no_coerce ) == dt_no_coerce
1859+
1860+
17891861def test_repeat (string_array ):
17901862 res = string_array .repeat (1000 )
17911863 # Create an empty array with expanded dimension, and fill it. Then,
0 commit comments