def test_simple_string_casts_roundtrip(self, other_dt, string_char): """ Tests casts from and to string by checking the roundtripping property. The test also covers some string to string casts (but not all). If this test creates issues, it should possibly just be simplified or even removed (checking whether unaligned/non-contiguous casts give the same results is useful, though). """ string_DT = type(np.dtype(string_char)) cast = get_castingimpl(type(other_dt), string_DT) cast_back = get_castingimpl(string_DT, type(other_dt)) _, (res_other_dt, string_dt) = cast._resolve_descriptors( (other_dt, None)) if res_other_dt is not other_dt: # do not support non-native byteorder, skip test in that case assert other_dt.byteorder != res_other_dt.byteorder return orig_arr, values = self.get_data(other_dt, None) str_arr = np.zeros(len(orig_arr), dtype=string_dt) string_dt_short = self.string_with_modified_length(string_dt, -1) str_arr_short = np.zeros(len(orig_arr), dtype=string_dt_short) string_dt_long = self.string_with_modified_length(string_dt, 1) str_arr_long = np.zeros(len(orig_arr), dtype=string_dt_long) assert not cast._supports_unaligned # if support is added, should test assert not cast_back._supports_unaligned for contig in [True, False]: other_arr, str_arr = self.get_data_variation( orig_arr, str_arr, True, contig) _, str_arr_short = self.get_data_variation(orig_arr, str_arr_short.copy(), True, contig) _, str_arr_long = self.get_data_variation(orig_arr, str_arr_long, True, contig) cast._simple_strided_call((other_arr, str_arr)) cast._simple_strided_call((other_arr, str_arr_short)) assert_array_equal(str_arr.astype(string_dt_short), str_arr_short) cast._simple_strided_call((other_arr, str_arr_long)) assert_array_equal(str_arr, str_arr_long) if other_dt.kind == "b": # Booleans do not roundtrip continue other_arr[...] = 0 cast_back._simple_strided_call((str_arr, other_arr)) assert_array_equal(orig_arr, other_arr) other_arr[...] = 0 cast_back._simple_strided_call((str_arr_long, other_arr)) assert_array_equal(orig_arr, other_arr)
def test_simple_cancast(self, from_Dt): for to_Dt in simple_dtypes: cast = get_castingimpl(from_Dt, to_Dt) for from_dt in [from_Dt(), from_Dt().newbyteorder()]: default = cast._resolve_descriptors((from_dt, None))[1][1] assert default == to_Dt() del default for to_dt in [to_Dt(), to_Dt().newbyteorder()]: casting, (from_res, to_res) = cast._resolve_descriptors( (from_dt, to_dt)) assert(type(from_res) == from_Dt) assert(type(to_res) == to_Dt) if casting & Casting.cast_is_view: # If a view is acceptable, this is "no" casting # and byte order must be matching. assert casting == Casting.no | Casting.cast_is_view # The above table lists this as "equivalent" assert Casting.equiv == CAST_TABLE[from_Dt][to_Dt] # Note that to_res may not be the same as from_dt assert from_res.isnative == to_res.isnative else: if from_Dt == to_Dt: # Note that to_res may not be the same as from_dt assert from_res.isnative != to_res.isnative assert casting == CAST_TABLE[from_Dt][to_Dt] if from_Dt is to_Dt: assert(from_dt is from_res) assert(to_dt is to_res)
class TestSimpleStridedCall: # Test mainly error paths of the resolve_descriptors function, # note that the `casting_unittests` tests exercise this non-error paths. # Casting implementations are the main/only current user: method = get_castingimpl(type(np.dtype("d")), type(np.dtype("f"))) @pytest.mark.parametrize( ["args", "error"], [ ((True, ), TypeError), # Not a tuple (((None, ), ), TypeError), # Too few elements ((None, None), TypeError), # Inputs are not arrays. (((None, None, None), ), TypeError), # Too many (((np.arange(3), np.arange(3)), ), TypeError), # Incorrect dtypes (((np.ones(3, dtype=">d"), np.ones(3, dtype="<f")), ), TypeError), # Does not support byte-swapping (((np.ones((2, 2), dtype="d"), np.ones( (2, 2), dtype="f")), ), ValueError), # not 1-D (((np.ones(3, dtype="d"), np.ones( 4, dtype="f")), ), ValueError), # different length (((np.frombuffer(b"\0x00" * 3 * 2, dtype="d"), np.frombuffer(b"\0x00" * 3, dtype="f")), ), ValueError), # output not writeable ]) def test_invalid_arguments(self, args, error): # This is private API, which may be modified freely with pytest.raises(error): self.method._simple_strided_call(*args)
def test_simple_direct_casts(self, from_dt): """ This test checks numeric direct casts for dtypes supported also by the struct module (plus complex). It tries to be test a wide range of inputs, but skips over possibly undefined behaviour (e.g. int rollover). Longdouble and CLongdouble are tested, but only using double precision. If this test creates issues, it should possibly just be simplified or even removed (checking whether unaligned/non-contiguous casts give the same results is useful, though). """ for to_dt in simple_dtype_instances(): to_dt = to_dt.values[0] cast = get_castingimpl(type(from_dt), type(to_dt)) casting, (from_res, to_res) = cast._resolve_descriptors( (from_dt, to_dt)) if from_res is not from_dt or to_res is not to_dt: # Do not test this case, it is handled in multiple steps, # each of which should is tested individually. return safe = (casting & ~Casting.cast_is_view) <= Casting.safe del from_res, to_res, casting arr1, arr2, values = self.get_data(from_dt, to_dt) cast._simple_strided_call((arr1, arr2)) # Check via python list assert arr2.tolist() == values # Check that the same results are achieved for strided loops arr1_o, arr2_o = self.get_data_variation(arr1, arr2, True, False) cast._simple_strided_call((arr1_o, arr2_o)) assert_array_equal(arr2_o, arr2) assert arr2_o.tobytes() == arr2.tobytes() # Check if alignment makes a difference, but only if supported # and only if the alignment can be wrong if ((from_dt.alignment == 1 and to_dt.alignment == 1) or not cast._supports_unaligned): return arr1_o, arr2_o = self.get_data_variation(arr1, arr2, False, True) cast._simple_strided_call((arr1_o, arr2_o)) assert_array_equal(arr2_o, arr2) assert arr2_o.tobytes() == arr2.tobytes() arr1_o, arr2_o = self.get_data_variation(arr1, arr2, False, False) cast._simple_strided_call((arr1_o, arr2_o)) assert_array_equal(arr2_o, arr2) assert arr2_o.tobytes() == arr2.tobytes() del arr1_o, arr2_o, cast
def test_simple_to_object_resolution(self, dtype): # Simple test to exercise the cast when no instance is specified object_dtype = type(np.dtype(object)) cast = get_castingimpl(type(dtype), object_dtype) safety, (_, res_dt) = cast._resolve_descriptors((dtype, None)) assert safety == Casting.safe assert res_dt is np.dtype("O")
def test_string_cancast(self, other_DT, string_char): fact = 1 if string_char == "S" else 4 string_DT = type(np.dtype(string_char)) cast = get_castingimpl(other_DT, string_DT) other_dt = other_DT() expected_length = get_expected_stringlength(other_dt) string_dt = np.dtype(f"{string_char}{expected_length}") safety, (res_other_dt, res_dt), view_off = cast._resolve_descriptors( (other_dt, None)) assert res_dt.itemsize == expected_length * fact assert safety == Casting.safe # we consider to string casts "safe" assert view_off is None assert isinstance(res_dt, string_DT) # These casts currently implement changing the string length, so # check the cast-safety for too long/fixed string lengths: for change_length in [-1, 0, 1]: if change_length >= 0: expected_safety = Casting.safe else: expected_safety = Casting.same_kind to_dt = self.string_with_modified_length(string_dt, change_length) safety, (_, res_dt), view_off = cast._resolve_descriptors( (other_dt, to_dt)) assert res_dt is to_dt assert safety == expected_safety assert view_off is None # The opposite direction is always considered unsafe: cast = get_castingimpl(string_DT, other_DT) safety, _, view_off = cast._resolve_descriptors((string_dt, other_dt)) assert safety == Casting.unsafe assert view_off is None cast = get_castingimpl(string_DT, other_DT) safety, (_, res_dt), view_off = cast._resolve_descriptors( (string_dt, None)) assert safety == Casting.unsafe assert view_off is None assert other_dt is res_dt # returns the singleton for simple dtypes
def test_object_to_parametric_internal_error(self): # We reject casting from object to a parametric type, without # figuring out the correct instance first. object_dtype = type(np.dtype(object)) other_dtype = type(np.dtype(str)) cast = get_castingimpl(object_dtype, other_dtype) with pytest.raises(TypeError, match="casting from object to the parametric DType"): cast._resolve_descriptors((np.dtype("O"), None))
def test_structured_view_offsets_paramteric(self, from_dt, to_dt, expected_off): # TODO: While this test is fairly thorough, right now, it does not # really test some paths that may have nonzero offsets (they don't # really exists). from_dt = np.dtype(from_dt) to_dt = np.dtype(to_dt) cast = get_castingimpl(type(from_dt), type(to_dt)) _, _, view_off = cast._resolve_descriptors((from_dt, to_dt)) assert view_off == expected_off
def test_object_and_simple_resolution(self, dtype): # Simple test to exercise the cast when no instance is specified object_dtype = type(np.dtype(object)) cast = get_castingimpl(object_dtype, type(dtype)) safety, (_, res_dt) = cast._resolve_descriptors((np.dtype("O"), dtype)) assert safety == Casting.unsafe assert res_dt is dtype safety, (_, res_dt) = cast._resolve_descriptors((np.dtype("O"), None)) assert safety == Casting.unsafe assert res_dt == dtype.newbyteorder("=")
def test_numeric_to_times(self, from_Dt): # We currently only implement contiguous loops, so only need to # test those. from_dt = from_Dt() time_dtypes = [ np.dtype("M8"), np.dtype("M8[ms]"), np.dtype("M8[4D]"), np.dtype("m8"), np.dtype("m8[ms]"), np.dtype("m8[4D]") ] for time_dt in time_dtypes: cast = get_castingimpl(type(from_dt), type(time_dt)) casting, (from_res, to_res), view_off = cast._resolve_descriptors( (from_dt, time_dt)) assert from_res is from_dt assert to_res is time_dt del from_res, to_res assert casting & CAST_TABLE[from_Dt][type(time_dt)] assert view_off is None int64_dt = np.dtype(np.int64) arr1, arr2, values = self.get_data(from_dt, int64_dt) arr2 = arr2.view(time_dt) arr2[...] = np.datetime64("NaT") if time_dt == np.dtype("M8"): # This is a bit of a strange path, and could probably be removed arr1[-1] = 0 # ensure at least one value is not NaT # The cast currently succeeds, but the values are invalid: cast._simple_strided_call((arr1, arr2)) with pytest.raises(ValueError): str(arr2[-1]) # e.g. conversion to string fails return cast._simple_strided_call((arr1, arr2)) assert [int(v) for v in arr2.tolist()] == values # Check that the same results are achieved for strided loops arr1_o, arr2_o = self.get_data_variation(arr1, arr2, True, False) cast._simple_strided_call((arr1_o, arr2_o)) assert_array_equal(arr2_o, arr2) assert arr2_o.tobytes() == arr2.tobytes()
def test_structured_field_offsets(self, to_dt, expected_off): # This checks the cast-safety and view offset for swapped and "shifted" # fields which are viewable from_dt = np.dtype({ "names": ["a", "b"], "formats": ["i4", "f4"], "offsets": [0, 4] }) cast = get_castingimpl(type(from_dt), type(to_dt)) safety, _, view_off = cast._resolve_descriptors((from_dt, to_dt)) assert safety == Casting.equiv # Shifting the original data pointer by -2 will align both by # effectively adding 2 bytes of spacing before `from_dt`. assert view_off == expected_off
def test_string_to_string_cancast(self, other_dt, string_char): other_dt = np.dtype(other_dt) fact = 1 if string_char == "S" else 4 div = 1 if other_dt.char == "S" else 4 string_DT = type(np.dtype(string_char)) cast = get_castingimpl(type(other_dt), string_DT) expected_length = other_dt.itemsize // div string_dt = np.dtype(f"{string_char}{expected_length}") safety, (res_other_dt, res_dt), view_off = cast._resolve_descriptors( (other_dt, None)) assert res_dt.itemsize == expected_length * fact assert isinstance(res_dt, string_DT) expected_view_off = None if other_dt.char == string_char: if other_dt.isnative: expected_safety = Casting.no expected_view_off = 0 else: expected_safety = Casting.equiv elif string_char == "U": expected_safety = Casting.safe else: expected_safety = Casting.unsafe assert view_off == expected_view_off assert expected_safety == safety for change_length in [-1, 0, 1]: to_dt = self.string_with_modified_length(string_dt, change_length) safety, (_, res_dt), view_off = cast._resolve_descriptors( (other_dt, to_dt)) assert res_dt is to_dt if change_length <= 0: assert view_off == expected_view_off else: assert view_off is None if expected_safety == Casting.unsafe: assert safety == expected_safety elif change_length < 0: assert safety == Casting.same_kind elif change_length == 0: assert safety == expected_safety elif change_length > 0: assert safety == Casting.safe
def test_time_to_time(self, from_dt, to_dt, expected_casting, expected_view_off, nom, denom): from_dt = np.dtype(from_dt) if to_dt is not None: to_dt = np.dtype(to_dt) # Test a few values for casting (results generated with NumPy 1.19) values = np.array([-2**63, 1, 2**63 - 1, 10000, -10000, 2**32]) values = values.astype( np.dtype("int64").newbyteorder(from_dt.byteorder)) assert values.dtype.byteorder == from_dt.byteorder assert np.isnat(values.view(from_dt)[0]) DType = type(from_dt) cast = get_castingimpl(DType, DType) casting, (from_res, to_res), view_off = cast._resolve_descriptors( (from_dt, to_dt)) assert from_res is from_dt assert to_res is to_dt or to_dt is None assert casting == expected_casting assert view_off == expected_view_off if nom is not None: expected_out = (values * nom // denom).view(to_res) expected_out[0] = "NaT" else: expected_out = np.empty_like(values) expected_out[...] = denom expected_out = expected_out.view(to_dt) orig_arr = values.view(from_dt) orig_out = np.empty_like(expected_out) if casting == Casting.unsafe and (to_dt == "m8" or to_dt == "M8"): # Casting from non-generic to generic units is an error and should # probably be reported as an invalid cast earlier. with pytest.raises(ValueError): cast._simple_strided_call((orig_arr, orig_out)) return for aligned in [True, True]: for contig in [True, True]: arr, out = self.get_data_variation(orig_arr, orig_out, aligned, contig) out[...] = 0 cast._simple_strided_call((arr, out)) assert_array_equal(out.view("int64"), expected_out.view("int64"))
class TestResolveDescriptors: # Test mainly error paths of the resolve_descriptors function, # note that the `casting_unittests` tests exercise this non-error paths. # Casting implementations are the main/only current user: method = get_castingimpl(type(np.dtype("d")), type(np.dtype("f"))) @pytest.mark.parametrize("args", [ (True,), # Not a tuple. ((None,)), # Too few elements ((None, None, None),), # Too many ((None, None),), # Input dtype is None, which is invalid. ((np.dtype("d"), True),), # Output dtype is not a dtype ((np.dtype("f"), None),), # Input dtype does not match method ]) def test_invalid_arguments(self, args): with pytest.raises(TypeError): self.method._resolve_descriptors(*args)