From 7566bcfeaba9b10b91a341741c08942a2bfea4eb Mon Sep 17 00:00:00 2001 From: Alex Valcourt Caron Date: Mon, 19 Dec 2022 14:03:04 -0500 Subject: [PATCH] Make apply-mocks fixture usable by all tests. Start hooks and fixtures modules for pytest, loaded using conftest.py in the script tests directory. --- pytest.ini | 9 ++- scilpy/tests/__init__.py | 1 + scilpy/tests/checks.py | 15 ++++ scilpy/tests/fixtures.py | 6 ++ scilpy/tests/hooks.py | 10 +++ scripts/tests/conftest.py | 6 ++ ...execute_angle_aware_bilateral_filtering.py | 77 ++++++++++++------- 7 files changed, 95 insertions(+), 29 deletions(-) create mode 100644 scilpy/tests/__init__.py create mode 100644 scilpy/tests/checks.py create mode 100644 scilpy/tests/fixtures.py create mode 100644 scilpy/tests/hooks.py create mode 100644 scripts/tests/conftest.py diff --git a/pytest.ini b/pytest.ini index 3f077269ed..f0bcdabb94 100644 --- a/pytest.ini +++ b/pytest.ini @@ -21,4 +21,11 @@ filterwarnings = once:::statsmodels once:::dmri-commit once:::cvxpy - once:::dmri-amico \ No newline at end of file + once:::dmri-amico + +usefixtures = + apply_mocks + +required_plugins = + pytest_console_scripts + pytest-mock \ No newline at end of file diff --git a/scilpy/tests/__init__.py b/scilpy/tests/__init__.py new file mode 100644 index 0000000000..991aa1a51e --- /dev/null +++ b/scilpy/tests/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scilpy/tests/checks.py b/scilpy/tests/checks.py new file mode 100644 index 0000000000..59e2ce5702 --- /dev/null +++ b/scilpy/tests/checks.py @@ -0,0 +1,15 @@ +import numpy as np + + +def assert_images_close(img1, img2): + dtype = img1.header.get_data_dtype() + + assert np.allclose(img1.affine, img2.affine), "Images affines don't match" + + assert np.allclose( + img1.get_fdata(dtype=dtype), img2.get_fdata(dtype=dtype)), \ + "Images data don't match. MSE : {} | max SE : {}".format( + np.mean((img1.get_fdata(dtype=dtype) - + img2.get_fdata(dtype=dtype)) ** 2.), + np.max((img1.get_fdata(dtype=dtype) - + img2.get_fdata(dtype=dtype)) ** 2.)) diff --git a/scilpy/tests/fixtures.py b/scilpy/tests/fixtures.py new file mode 100644 index 0000000000..b3f1bffc31 --- /dev/null +++ b/scilpy/tests/fixtures.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture(scope="session") +def apply_mocks(request): + return request.config.getoption("--apply-mocks") diff --git a/scilpy/tests/hooks.py b/scilpy/tests/hooks.py new file mode 100644 index 0000000000..005b9b8be9 --- /dev/null +++ b/scilpy/tests/hooks.py @@ -0,0 +1,10 @@ +import pytest + + +def pytest_addoption(parser): + parser.addoption( + "--apply-mocks", + action="store_true", + help="Apply mocks to accelerate tests and " + "prevent testing external dependencies" + ) diff --git a/scripts/tests/conftest.py b/scripts/tests/conftest.py new file mode 100644 index 0000000000..556fecf63a --- /dev/null +++ b/scripts/tests/conftest.py @@ -0,0 +1,6 @@ +import pytest + +pytest_plugins = [ + "scilpy.tests.fixtures", + "scilpy.tests.hooks" +] diff --git a/scripts/tests/test_execute_angle_aware_bilateral_filtering.py b/scripts/tests/test_execute_angle_aware_bilateral_filtering.py index 35b16becac..bfca4fcc8d 100644 --- a/scripts/tests/test_execute_angle_aware_bilateral_filtering.py +++ b/scripts/tests/test_execute_angle_aware_bilateral_filtering.py @@ -6,8 +6,10 @@ import os import pytest import tempfile +from shutil import copyfile from scilpy.io.fetcher import get_testing_files_dict, fetch_data, get_home +from scilpy.tests.checks import assert_images_close # If they already exist, this only takes 5 seconds (check md5sum) @@ -16,16 +18,20 @@ tmp_dir = tempfile.TemporaryDirectory() -@pytest.fixture -def mock_filtering(mocker, out_fodf): - def _mock(*args, **kwargs): - img = nib.load(out_fodf) - return img.get_fdata().astype(np.float32) +@pytest.fixture(scope='function') +def filter_mock(mocker, apply_mocks, out_fodf): + if apply_mocks: + def _mock_side_effect(*args, **kwargs): + img = nib.load(out_fodf) + return img.get_fdata(dtype=np.float32) - script = 'scil_execute_angle_aware_bilateral_filtering' - filtering_fn = "angle_aware_bilateral_filtering" - return mocker.patch("scripts.{}.{}".format(script, filtering_fn), - side_effect=_mock, create=True) + return mocker.patch( + "{}.{}".format( + "scripts.scil_execute_angle_aware_bilateral_filtering", + "angle_aware_bilateral_filtering"), + side_effect=_mock_side_effect, create=True) + + return None def test_help_option(script_runner): @@ -36,8 +42,10 @@ def test_help_option(script_runner): @pytest.mark.parametrize("in_fodf,out_fodf", [[os.path.join(data_path, 'fodf_descoteaux07_sub.nii.gz'), - os.path.join(data_path, 'fodf_descoteaux07_sub_full.nii.gz')]]) -def test_asym_basis_output(script_runner, mock_filtering, in_fodf, out_fodf): + os.path.join(data_path, 'fodf_descoteaux07_sub_full.nii.gz')]], + scope='function') +def test_asym_basis_output( + script_runner, filter_mock, apply_mocks, in_fodf, out_fodf): os.chdir(os.path.expanduser(tmp_dir.name)) ret = script_runner.run('scil_execute_angle_aware_bilateral_filtering.py', @@ -52,19 +60,23 @@ def test_asym_basis_output(script_runner, mock_filtering, in_fodf, out_fodf): print_result=True, shell=True) assert ret.success - mock_filtering.assert_called_once() - ret_fodf = nib.load("out_fodf1.nii.gz") - test_fodf = nib.load(out_fodf) - assert np.allclose(ret_fodf.get_fdata(), test_fodf.get_fdata()) + if apply_mocks: + filter_mock.assert_called_once() + + copyfile(in_fodf, "/mnt/d/in_fodf1.nii.gz") + copyfile("out_fodf1.nii.gz", "/mnt/d/out_fodf1.nii.gz") + copyfile(out_fodf, "/mnt/d/out_fodf1_cmp.nii.gz") + assert_images_close(nib.load(out_fodf), nib.load("out_fodf1.nii.gz")) @pytest.mark.parametrize("in_fodf,out_fodf,sym_fodf", [[os.path.join(data_path, "fodf_descoteaux07_sub.nii.gz"), os.path.join(data_path, "fodf_descoteaux07_sub_full.nii.gz"), - os.path.join(data_path, "fodf_descoteaux07_sub_sym.nii.gz")]]) + os.path.join(data_path, "fodf_descoteaux07_sub_sym.nii.gz")]], + scope='function') def test_sym_basis_output( - script_runner, mock_filtering, in_fodf, out_fodf, sym_fodf): + script_runner, filter_mock, apply_mocks, in_fodf, out_fodf, sym_fodf): os.chdir(os.path.expanduser(tmp_dir.name)) ret = script_runner.run('scil_execute_angle_aware_bilateral_filtering.py', @@ -80,17 +92,23 @@ def test_sym_basis_output( print_result=True, shell=True) assert ret.success - mock_filtering.assert_called_once() - ret_sym_fodf = nib.load("out_sym.nii.gz") - test_sym_fodf = nib.load(sym_fodf) - assert np.allclose(ret_sym_fodf.get_fdata(), test_sym_fodf.get_fdata()) + if apply_mocks: + filter_mock.assert_called_once() + + copyfile(in_fodf, "/mnt/d/in_fodf2.nii.gz") + copyfile("out_fodf2.nii.gz", "/mnt/d/out_fodf2.nii.gz") + copyfile(out_fodf, "/mnt/d/out_fodf2_cmp.nii.gz") + copyfile("out_sym.nii.gz", "/mnt/d/out_sym.nii.gz") + copyfile(sym_fodf, "/mnt/d/out_sym_cmp.nii.gz") + assert_images_close(nib.load(sym_fodf), nib.load("out_sym.nii.gz")) @pytest.mark.parametrize("in_fodf,out_fodf", [[os.path.join(data_path, "fodf_descoteaux07_sub_full.nii.gz"), - os.path.join(data_path, "fodf_descoteaux07_sub_twice.nii.gz")]]) -def test_asym_input(script_runner, mock_filtering, in_fodf, out_fodf): + os.path.join(data_path, "fodf_descoteaux07_sub_twice.nii.gz")]], + scope='function') +def test_asym_input(script_runner, filter_mock, apply_mocks, in_fodf, out_fodf): os.chdir(os.path.expanduser(tmp_dir.name)) ret = script_runner.run('scil_execute_angle_aware_bilateral_filtering.py', @@ -105,8 +123,11 @@ def test_asym_input(script_runner, mock_filtering, in_fodf, out_fodf): print_result=True, shell=True) assert ret.success - mock_filtering.assert_called_once() - - ret_fodf = nib.load("out_fodf3.nii.gz") - test_fodf = nib.load(out_fodf) - assert np.allclose(ret_fodf.get_fdata(), test_fodf.get_fdata()) + + if apply_mocks: + filter_mock.assert_called_once() + + copyfile(in_fodf, "/mnt/d/in_fodf3.nii.gz") + copyfile("out_fodf3.nii.gz", "/mnt/d/out_fodf3.nii.gz") + copyfile(out_fodf, "/mnt/d/out_fodf3_cmp.nii.gz") + assert_images_close(nib.load(out_fodf), nib.load("out_fodf3.nii.gz"))