diff --git a/tests/conftest.py b/tests/conftest.py index c854b3f..1d12fa8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ # Temporary SOFA-file -@pytest.fixture +@pytest.fixture() def temp_sofa_file(tmp_path_factory): """ Temporary small SOFA file. diff --git a/tests/test_conventions_dimensions_consistency.py b/tests/test_conventions_dimensions_consistency.py new file mode 100644 index 0000000..9e80246 --- /dev/null +++ b/tests/test_conventions_dimensions_consistency.py @@ -0,0 +1,86 @@ +""" +This tests for consistent dimensions in the convention files. + +All dimensions must be of the same length. E.g., the set of dimensions 'M, ME' +is invalid and should be 'MI, 'ME'. + +This cannot be done as part of Sofa.verify, because it would make it impossible +to read SOFA files written with deprecated conventions that are containing +inconsistent dimensions. +""" +import sofar as sf +import numpy as np +import pytest +import json + +convention_paths = sf.utils._get_conventions('path') + + +def check_dimensions_consistency(convention_path): + """ + Check for consistent dimensions. + + Parameters + ---------- + convention_path : str + path to the SOFA convention to be checked. Must be a json file. + + Raises + ------ + ValueError if one or more inconsistent dimensions are found. + """ + + with open(convention_path, "r") as file: + convention = json.load(file) + + name = convention['GLOBAL:SOFAConventions']['default'] + ' v' + \ + convention['GLOBAL:SOFAConventionsVersion']['default'] + + errors = [] + + for key, value in convention.items(): + + dimensions = value['dimensions'] + + if dimensions is None: + continue + + dim_length = [len(dim) for dim in dimensions.split(", ")] + if len(np.unique(dim_length)) > 1: + + # get verbose string for error message containing the number of + # dimensions and the actual dimensions, e.g., 2 (MR), 3 (MRE) + dim_length_string = [] + for length, dim in zip(dim_length, dimensions.split(", ")): + dim_length_string.append(f'{length} ({dim})') + + errors.append(f'{key}: {", ".join(dim_length_string)}') + + # raise error at the end in case there are multiple inconsistencies + if len(errors): + raise ValueError((f'Found dimensions of unequal length for {name}: ' + f'{"; ".join(errors)}')) + + +@pytest.mark.parametrize('convention_path', convention_paths) +def test_dimensions_consistency(convention_path): + """ + Check up to date conventions and skip deprecated. + This must not raise errors. + """ + + if 'deprecated' in convention_path: + return 0 + + check_dimensions_consistency(convention_path) + + +def test_dimensions_consistency_error(): + """Check selected deprecated convention. This must raise and error.""" + + for convention_path in convention_paths: + if convention_path.endswith('FreeFieldDirectivityTF_1.0.json'): + break + + with pytest.raises(ValueError, match='Found dimensions of unequal length'): + check_dimensions_consistency(convention_path) diff --git a/tests/test_io.py b/tests/test_io.py index 6186b4d..aab771e 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -197,12 +197,13 @@ def test_roundtrip_multidimensional_string_variable(): # add dummy matrix that contains 4 measurements sofa.Data_IR = np.zeros((4, 2, 10)) # add (4, 1) string variable - sofa.SourceManufacturer = [["someone"], ["else"], ["did"], ["this"]] + sofa.SourceManufacturers = [["someone"], ["else"], ["did"], ["this"]] # remove other string variables for simplicity - delattr(sofa, "SourceModel") + delattr(sofa, "SourceModels") delattr(sofa, "ReceiverDescriptions") delattr(sofa, "EmitterDescriptions") delattr(sofa, "MeasurementDate") + delattr(sofa, "SourceURIs") # read write and assert sf.write_sofa(file, sofa) diff --git a/tests/test_sofa.py b/tests/test_sofa.py index ad0474e..60a52b8 100644 --- a/tests/test_sofa.py +++ b/tests/test_sofa.py @@ -303,13 +303,13 @@ def test_delete_entry(): sofa = sf.Sofa("SimpleHeadphoneIR") assert hasattr(sofa, "GLOBAL_History") - assert hasattr(sofa, "SourceManufacturer") + assert hasattr(sofa, "SourceManufacturers") # delete one optional attribute and variable sofa.delete("GLOBAL_History") - sofa.delete("SourceManufacturer") + sofa.delete("SourceManufacturers") # check if data were removed assert not hasattr(sofa, "GLOBAL_History") - assert not hasattr(sofa, "SourceManufacturer") + assert not hasattr(sofa, "SourceManufacturers") def test__get_size_and_shape_of_string_var(): diff --git a/tests/test_sofa_verify.py b/tests/test_sofa_verify.py index 497f055..77cfc5d 100644 --- a/tests/test_sofa_verify.py +++ b/tests/test_sofa_verify.py @@ -227,18 +227,18 @@ def test_data_types(capfd): # test invalid data for netCDF string variable sofa = sf.Sofa("SimpleHeadphoneIR") - sofa.SourceModel = 1 - with pytest.raises(ValueError, match="- SourceModel must be string"): + sofa.SourceModels = 1 + with pytest.raises(ValueError, match="- SourceModels must be string"): sofa.verify() - sofa.SourceModel = np.array(1) - with pytest.raises(ValueError, match="- SourceModel must be U or S"): + sofa.SourceModels = np.array(1) + with pytest.raises(ValueError, match="- SourceModels must be U or S"): sofa.verify() # test valid data - sofa.SourceModel = ["test"] + sofa.SourceModels = ["test"] sofa.verify() - sofa.SourceModel = np.array(["test"]) + sofa.SourceModels = np.array(["test"]) sofa.verify() diff --git a/tests/test_utils.py b/tests/test_utils.py index 3bdb6e4..d6871f4 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -224,10 +224,10 @@ def test_equals_global_parameters(): ("1", "2", "GLOBAL_SOFAConventionsVersion", True), ([[1, 2]], [1, 2], "Data_IR", False), ([[1, 2]], [1, 3], "Data_IR", True), - ("HD 650", ["HD 650"], "SourceModel", False), - ("HD 650", np.array(["HD 650"], dtype="U"), "SourceModel", False), - ("HD 650", np.array(["HD 650"], dtype="S"), "SourceModel", False), - ("HD 650", "HD-650", "SourceModel", True), + ("HD 650", ["HD 650"], "SourceModels", False), + ("HD 650", np.array(["HD 650"], dtype="U"), "SourceModels", False), + ("HD 650", np.array(["HD 650"], dtype="S"), "SourceModels", False), + ("HD 650", "HD-650", "SourceModels", True), ]) def test_equals_attribute_values(value_a, value_b, attribute, fails):