diff --git a/news/pytest-warning-divide-zero.rst b/news/pytest-warning-divide-zero.rst new file mode 100644 index 00000000..4b84dba2 --- /dev/null +++ b/news/pytest-warning-divide-zero.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: previous news created for catching divide by zero warnings in pytest + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 220a4aad..4576b257 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -373,15 +373,16 @@ def test_dump(tmp_path, mocker): x, y = np.linspace(0, 5, 6), np.linspace(0, 5, 6) directory = Path(tmp_path) file = directory / "testfile" - do = DiffractionObject( - wavelength=1.54, - name="test", - scat_quantity="x-ray", - xarray=np.array(x), - yarray=np.array(y), - xtype="q", - metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, - ) + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + do = DiffractionObject( + wavelength=1.54, + name="test", + scat_quantity="x-ray", + xarray=np.array(x), + yarray=np.array(y), + xtype="q", + metadata={"thing1": 1, "thing2": "thing2", "package_info": {"package2": "3.4.5"}}, + ) mocker.patch("importlib.metadata.version", return_value="3.3.0") with freeze_time("2012-01-14"): do.dump(file, "q") @@ -402,114 +403,107 @@ def test_dump(tmp_path, mocker): assert actual == expected -test_init_valid_params = [ - ( # instantiate just array attributes - { - "xarray": np.array([0.0, 90.0, 180.0]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "tth", - "wavelength": 4.0 * np.pi, - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "tth", - "name": "", - "scat_quantity": "", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), - ( # instantiate just array attributes - { - "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), - "yarray": np.array([1.0, 2.0, 3.0]), - "xtype": "d", - "wavelength": 4.0 * np.pi, - "scat_quantity": "x-ray", - }, - { - "_all_arrays": np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ), - "metadata": {}, - "_input_xtype": "d", - "name": "", - "scat_quantity": "x-ray", - "qmin": np.float64(0.0), - "qmax": np.float64(1.0), - "tthmin": np.float64(0.0), - "tthmax": np.float64(180.0), - "dmin": np.float64(2 * np.pi), - "dmax": np.float64(np.inf), - "wavelength": 4.0 * np.pi, - }, - ), -] - - @pytest.mark.parametrize( - "init_args, expected_do_dict", - test_init_valid_params, + "do_init_args, expected_do_dict, divide_by_zero_warning_expected", + [ + ( # instantiate just array attributes + { + "xarray": np.array([0.0, 90.0, 180.0]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "tth", + "wavelength": 4.0 * np.pi, + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "tth", + "name": "", + "scat_quantity": "", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + True, + ), + ( # instantiate just array attributes + { + "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), + "yarray": np.array([1.0, 2.0, 3.0]), + "xtype": "d", + "wavelength": 4.0 * np.pi, + "scat_quantity": "x-ray", + }, + { + "_all_arrays": np.array( + [ + [1.0, 0.0, 0.0, np.float64(np.inf)], + [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], + [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], + ] + ), + "metadata": {}, + "_input_xtype": "d", + "name": "", + "scat_quantity": "x-ray", + "qmin": np.float64(0.0), + "qmax": np.float64(1.0), + "tthmin": np.float64(0.0), + "tthmax": np.float64(180.0), + "dmin": np.float64(2 * np.pi), + "dmax": np.float64(np.inf), + "wavelength": 4.0 * np.pi, + }, + False, + ), + ], ) -def test_init_valid(init_args, expected_do_dict): - actual_do_dict = DiffractionObject(**init_args).__dict__ +def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expected): + if divide_by_zero_warning_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_do_dict = DiffractionObject(**do_init_args).__dict__ + else: + actual_do_dict = DiffractionObject(**do_init_args).__dict__ diff = DeepDiff( actual_do_dict, expected_do_dict, ignore_order=True, significant_digits=13, exclude_paths="root['_id']" ) assert diff == {} -test_init_invalid_params = [ - ( # UC1: no arguments provided - {}, - "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", - ), - ( # UC2: only xarray and yarray provided - {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, - "missing 1 required positional argument: 'xtype'", - ), -] - - -@pytest.mark.parametrize("init_args, expected_error_msg", test_init_invalid_params) +@pytest.mark.parametrize( + "do_init_args, expected_error_msg", + [ + ( # Case 1: no arguments provided + {}, + "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", + ), + ( # Case 2: only xarray and yarray provided + {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, + "missing 1 required positional argument: 'xtype'", + ), + ], +) def test_init_invalid_args( - init_args, + do_init_args, expected_error_msg, ): with pytest.raises(TypeError, match=expected_error_msg): - DiffractionObject(**init_args) + DiffractionObject(**do_init_args) -def test_all_array_getter(): - actual_do = DiffractionObject( - xarray=np.array([0.0, 90.0, 180.0]), - yarray=np.array([1.0, 2.0, 3.0]), - xtype="tth", - wavelength=4.0 * np.pi, - ) - expected_all_arrays = np.array( - [ - [1.0, 0.0, 0.0, np.float64(np.inf)], - [2.0, 1.0 / np.sqrt(2), 90.0, np.sqrt(2) * 2 * np.pi], - [3.0, 1.0, 180.0, 1.0 * 2 * np.pi], - ] - ) +def test_all_array_getter(do_minimal_tth): + actual_do = do_minimal_tth + print(actual_do.all_arrays) + expected_all_arrays = [[1, 0.51763809, 30, 12.13818192], [2, 1, 60, 6.28318531]] assert np.allclose(actual_do.all_arrays, expected_all_arrays) @@ -574,14 +568,8 @@ def test_input_xtype_setter_error(do_minimal): do.input_xtype = "q" -def test_copy_object(): - do = DiffractionObject( - name="test", - wavelength=4.0 * np.pi, - xarray=np.array([0.0, 90.0, 180.0]), - yarray=np.array([1.0, 2.0, 3.0]), - xtype="tth", - ) +def test_copy_object(do_minimal): + do = do_minimal do_copy = do.copy() assert do == do_copy assert id(do) != id(do_copy) diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 7a3bdeef..65f437ac 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -146,50 +146,55 @@ def test_q_to_d(q, expected_d, warning_expected): @pytest.mark.parametrize( - "d, expected_q", + "d, expected_q, zero_divide_error_expected", [ # UC1: User specified empty d values - (np.array([]), np.array([])), + (np.array([]), np.array([]), False), # UC2: User specified valid d values ( np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), + True, ), ], ) -def test_d_to_q(d, expected_q): - actual_q = d_to_q(d) +def test_d_to_q(d, expected_q, zero_divide_error_expected): + if zero_divide_error_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_q = d_to_q(d) + else: + actual_q = d_to_q(d) assert np.allclose(actual_q, expected_q) @pytest.mark.parametrize( - "wavelength, tth, expected_d", + "wavelength, tth, expected_d, divide_by_zero_warning_expected", [ - # UC0: User specified empty tth values (without wavelength) - (None, np.array([]), np.array([])), - # UC1: User specified empty tth values (with wavelength) - (4 * np.pi, np.array([]), np.array([])), - # UC2: User specified valid tth values between 0-180 degrees (without wavelength) - ( - None, - np.array([0, 30, 60, 90, 120, 180]), - np.array([0, 1, 2, 3, 4, 5]), - ), - # UC3: User specified valid tth values between 0-180 degrees (with wavelength) + # Test conversion of q to d with valid values + # Case 1: empty tth values, no, expect empty d values + (None, np.array([]), np.array([]), False), + # Case 2: empty tth values, wavelength provided, expect empty d values + (4 * np.pi, np.array([]), np.array([]), False), + # Case 3: User specified valid tth values between 0-180 degrees (without wavelength) + (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), + # Case 4: User specified valid tth values between 0-180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), np.array([np.inf, 24.27636, 12.56637, 8.88577, 7.25520, 6.28319]), + True, ), ], ) -def test_tth_to_d(wavelength, tth, expected_d, wavelength_warning_msg): +def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, wavelength_warning_msg): if wavelength is None: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_d = tth_to_d(tth, wavelength) + elif divide_by_zero_warning_expected: + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_d = tth_to_d(tth, wavelength) else: actual_d = tth_to_d(tth, wavelength) - assert np.allclose(actual_d, expected_d) @@ -218,30 +223,31 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m @pytest.mark.parametrize( - "wavelength, d, expected_tth", + "wavelength, d, expected_tth, divide_by_zero_warning_expected", [ # UC1: Empty d values, no wavelength, return empty arrays - (None, np.empty((0)), np.empty((0))), + (None, np.empty((0)), np.empty((0)), False), # UC2: Empty d values, wavelength specified, return empty arrays - (4 * np.pi, np.empty((0)), np.empty(0)), + (4 * np.pi, np.empty((0)), np.empty(0), False), # UC3: User specified valid d values, no wavelength, return empty arrays - ( - None, - np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), - np.array([0, 1, 2, 3, 4, 5]), - ), + (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), # UC4: User specified valid d values (with wavelength) ( 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), np.array([60.0, 90.0, 120.0]), + False, ), ], ) -def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg): - if wavelength is None: +def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, wavelength_warning_msg): + if wavelength is None and not divide_by_zero_warning_expected: with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): actual_tth = d_to_tth(d, wavelength) + elif wavelength is None and divide_by_zero_warning_expected: + with pytest.warns(UserWarning, match=re.escape(wavelength_warning_msg)): + with pytest.warns(RuntimeWarning, match="divide by zero encountered in divide"): + actual_tth = d_to_tth(d, wavelength) else: actual_tth = d_to_tth(d, wavelength) @@ -254,7 +260,7 @@ def test_d_to_tth(wavelength, d, expected_tth, wavelength_warning_msg): # UC1: user specified invalid d values that result in tth > 180 degrees (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), # UC2: user specified a wrong wavelength that result in tth > 180 degrees - (100, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), ValueError), + (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], ) def test_d_to_tth_bad(wavelength, d, expected_error_type, invalid_q_or_d_or_wavelength_error_msg):