From 2cc2ce7226900093f65880a91b3da2b91adced76 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauldmccarthy@gmail.com> Date: Thu, 9 Mar 2023 19:40:50 +0000 Subject: [PATCH] TEST: unit tests for config module --- bip/tests/__init__.py | 42 ++++ bip/tests/test_bip_utils_config.py | 318 +++++++++++++++++++++++++++++ 2 files changed, 360 insertions(+) create mode 100644 bip/tests/__init__.py create mode 100644 bip/tests/test_bip_utils_config.py diff --git a/bip/tests/__init__.py b/bip/tests/__init__.py new file mode 100644 index 0000000..4428cf8 --- /dev/null +++ b/bip/tests/__init__.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# +# Utility functions available for use by tests. +# + + +import contextlib +import os +import os.path as op +import tempfile + + +def touch(fpath): + with open(fpath, 'wt') as f: + f.write(fpath) + + +@contextlib.contextmanager +def tempdir(): + prevdir = os.getcwd() + with tempfile.TemporaryDirectory() as td: + os.chdir(td) + try: + yield td + finally: + os.chdir(prevdir) + + +@contextlib.contextmanager +def mock_directory(contents): + with tempdir() as td: + for c in contents: + touch(c) + yield td + + +def dicts_equal(da, db): + da = {k : da[k] for k in sorted(da.keys())} + db = {k : db[k] for k in sorted(db.keys())} + print(da) + print(db) + return da == db diff --git a/bip/tests/test_bip_utils_config.py b/bip/tests/test_bip_utils_config.py new file mode 100644 index 0000000..6ef4744 --- /dev/null +++ b/bip/tests/test_bip_utils_config.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python + +import os.path as op +import textwrap as tw + +import unittest + +import pytest + +import bip.utils.config as config + +from bip.tests import touch, tempdir, mock_directory, dicts_equal + + +def test_nested_lookup(): + # (dictionary, key, expected_value) + tests = [ + ({'a' : {'b' : {'c' : {'d' : 'e'}}}}, ['a'], + {'b' : {'c' : {'d' : 'e'}}}), + ({'a' : {'b' : {'c' : {'d' : 'e'}}}}, ['a', 'b'], + {'c' : {'d' : 'e'}}), + ({'a' : {'b' : {'c' : {'d' : 'e'}}}}, ['a', 'b', 'c'], + {'d' : 'e'}), + ({'a' : {'b' : {'c' : {'d' : 'e'}}}}, ['a', 'b', 'c', 'd'], + 'e'), + ] + + for d, k, exp in tests: + assert config.nested_lookup(d, k) == exp + + +def test_flatten_dictionary(): + # (indict, nlevels, expdict) + tests = [ + ({'a' : {'b' : 'c', 'd' : 'e'}}, 1, + {'a_b' : 'c', 'a_d' : 'e'}), + + ({'a' : 'b', 'c' : {'d' : 'e', 'f' : 'g'}}, 1, + {'a' : 'b', 'c_d' : 'e', 'c_f' : 'g'}), + + ({'a' : 'b', 'c' : {'d' : 'e', 'f' : {'g' : 'h', 'i' : 'j'}}}, 1, + {'a' : 'b', 'c_d' : 'e', 'c_f' : {'g' : 'h', 'i' : 'j'}}), + + ({'a' : 'b', 'c' : {'d' : 'e', 'f' : {'g' : 'h', 'i' : 'j'}}}, 2, + {'a' : 'b', 'c_d' : 'e', 'c_f_g' : 'h', 'c_f_i' : 'j'}), + ] + + for indict, nlevels, expdict in tests: + assert config.flatten_dictionary(indict, nlevels) == expdict + + +def test_parse_override_value(): + # (template value, input value, expected) + tests = [ + ('a', 'b', 'b'), + (None, 'b', 'b'), + + (1, '1', 1), + (1.5, '1', 1), + (1, '1.5', 1.5), + (1.5, '1.5', 1.5), + + (True, 'false', False), + (True, 'true', True), + + ([], '[1,2,3,4]', [1, 2, 3, 4]), + ({}, '{"a" = 1, "b" = 2}', {'a' : 1, 'b' : 2}) + ] + + for origval, val, expect in tests: + assert config.parse_override_value('param', origval, val) == expect + + +def test_Config_config_file_identifier(): + # (filename, expected) + tests = [ + ('config.toml', 'config'), + ('T1_struct.toml', 'T1_struct'), + ('01.T1_struct.toml', 'T1_struct'), + ('01.02.T1_struct.toml', 'T1_struct'), + ] + + for filename, expected in tests: + assert config.Config.config_file_identifier(filename) == expected + + +def test_Config_list_config_files(): + contents = ['config.toml', 'config.json', 'config.cfg', + 'abcde.toml', 'bcdef.toml', 'cdefg.toml', + '02.defgh.toml', 'some_file.json'] + # files should be in alphabetical + # order, with config.toml last + expect = ['02.defgh.toml', 'abcde.toml', 'bcdef.toml', + 'cdefg.toml', 'config.toml'] + with mock_directory(contents) as mdir: + expect = [op.join(e) for e in expect] + assert config.Config.list_config_files(mdir) + + +def test_Config_resolve_selectors(): + + indict1 = {'param1' : '1', + 'param2' : '2', + 'subject' : {'12345' : {'param1' : '3', 'param2' : '4'}, + '56789' : {'param1' : '5', 'param2' : '6'}}} + + indict2 = {'param1' : '1', + 'param2' : '2', + 'subject' : {'12345' : {'param1' : '3', + 'visit' : {'2' : {'param1' : '3.2', + 'param2' : '4.2'}}}, + '56789' : {'param1' : '5', + 'visit' : {'2' : {'param1' : '5.2', + 'param2' : '6.2'}}}}} + + # (input dict, selectors, expected output) + tests = [ + (indict1, {}, {'param1' : '1', 'param2' : '2'}), + (indict1, {'subject' : '12345'}, {'param1' : '3', 'param2' : '4'}), + (indict1, {'subject' : '56789'}, {'param1' : '5', 'param2' : '6'}), + + (indict2, {}, {'param1' : '1', 'param2' : '2'}), + (indict2, {'subject' : '12345'}, {'param1' : '3', 'param2' : '2'}), + (indict2, {'subject' : '56789'}, {'param1' : '5', 'param2' : '2'}), + (indict2, {'visit' : '2'}, {'param1' : '1', 'param2' : '2'}), + (indict2, {'subject' : '12345', + 'visit' : '1'}, {'param1' : '3', 'param2' : '2'}), + (indict2, {'subject' : '56789', + 'visit' : '1'}, {'param1' : '5', 'param2' : '2'}), + (indict2, {'subject' : '12345', + 'visit' : '2'}, {'param1' : '3.2', 'param2' : '4.2'}), + (indict2, {'subject' : '56789', + 'visit' : '2'}, {'param1' : '5.2', 'param2' : '6.2'}), + ] + + for indict, selectors, expdict in tests: + result = config.Config.resolve_selectors(indict, selectors) + for k, v in expdict.items(): + assert result[k] == v + + +def test_Config_load_config_file(): + + configtoml = tw.dedent(""" + param1 = 1 + param2 = 2 + subject.12345.param1 = 3 + subject.56789.param1 = 5 + subject.12345.visit.2.param1 = 3.2 + subject.12345.visit.2.param2 = 4.2 + subject.56789.visit.2.param1 = 5.2 + subject.56789.visit.2.param2 = 6.2 + """).strip() + # (selectors, expected output) + tests = [ + ({}, {'param1' : 1, 'param2' : 2}), + ({'subject' : '12345'}, {'param1' : 3, 'param2' : 2}), + ({'subject' : '56789'}, {'param1' : 5, 'param2' : 2}), + ({'visit' : '2'}, {'param1' : 1, 'param2' : 2}), + ({'subject' : '12345', + 'visit' : '1'}, {'param1' : 3, 'param2' : 2}), + ({'subject' : '56789', + 'visit' : '1'}, {'param1' : 5, 'param2' : 2}), + ({'subject' : '12345', + 'visit' : '2'}, {'param1' : 3.2, 'param2' : 4.2}), + ({'subject' : '56789', + 'visit' : '2'}, {'param1' : 5.2, 'param2' : 6.2}), + ] + + with tempdir(): + fnames = ['config.toml', 'abc.toml'] + for fname in fnames: + open(fname, 'wt').write(configtoml) + for selectors, expect in tests: + result = config.Config.load_config_file(fname, selectors) + for k, v in expect.items(): + + if fname != 'config.toml': + ident = config.Config.config_file_identifier(fname) + k = f'{ident}_{k}' + assert result[k] == v + + +def test_Config_load_config_file_main_relabelling(): + configtoml = tw.dedent(""" + param1 = 1 + param2 = 2 + [abc] + param3 = 3 + param4 = 4 + [def] + param5 = 5 + param6 = 6 + """).strip() + + expect = { + 'param1' : 1, + 'param2' : 2, + 'abc_param3' : 3, + 'abc_param4' : 4, + 'def_param5' : 5, + 'def_param6' : 6 + } + + with tempdir(): + open('config.toml', 'wt').write(configtoml) + result = config.Config.load_config_file('config.toml') + assert result == expect + + +def test_Config_create(): + configtoml = tw.dedent(""" + param1 = 0.5 + abc_param1 = 0.4 + def_param1 = 0.3 + """).strip() + abctoml = tw.dedent(""" + param1 = 0.6 + param2 = 0.7 + """).strip() + def01toml = tw.dedent(""" + param1 = 0.8 + param2 = 0.9 + """).strip() + def02toml = tw.dedent(""" + param1 = 1.0 + param2 = 1.1 + """).strip() + + exp = { + 'param1' : 0.5, + 'abc_param1' : 0.4, + 'def_param1' : 0.3, + 'abc_param2' : 0.7, + 'def_param2' : 1.1, + } + + with tempdir(): + open('config.toml', 'wt').write(configtoml) + open('abc.toml', 'wt').write(abctoml) + open('01.def.toml', 'wt').write(def01toml) + open('02.def.toml', 'wt').write(def02toml) + cfg = config.Config('.') + + for k, v in exp.items(): + assert cfg[k] == v + + +def test_Config_overrides(): + configtoml = tw.dedent(""" + param1 = 1 + param2 = 'abc' + param3 = [1, 2, 3] + [abc] + param1 = true + param2 = 0.4 + """).strip() + + # (overrides, expected) + pass_tests = [ + ({}, + {'param1' : 1, + 'param2' : 'abc', + 'param3' : [1, 2, 3], + 'abc_param1' : True, + 'abc_param2' : 0.4}), + ({'param2' : 'def', + 'abc_param2' : '0.7'}, + {'param1' : 1, + 'param2' : 'def', + 'param3' : [1, 2, 3], + 'abc_param1' : True, + 'abc_param2' : 0.7}), + ({'param1' : '8', + 'abc_param1' : 'false'}, + {'param1' : 8, + 'param2' : 'abc', + 'param3' : [1, 2, 3], + 'abc_param1' : False, + 'abc_param2' : 0.4}), + ({'param4' : 'ghi', + 'param5' : '[1,2,3,4]', + 'abc_param3' : '7.5'}, + {'param1' : 1, + 'param2' : 'abc', + 'param3' : [1, 2, 3], + 'param4' : 'ghi', + 'param5' : [1, 2, 3, 4], + 'abc_param1' : True, + 'abc_param2' : 0.4, + 'abc_param3' : 7.5}), + ] + + # Overrides should be rejected when + # their type (as inferred by tomllib + # parsing) does not match the type of + # the stored value. + fail_tests = [ + {'param1' : 'abc'}, + {'param3' : '1.5'}, + {'abc_param1' : 'abc'}, + {'abc_param2' : 'abc'}, + ] + + with tempdir(): + open('config.toml', 'wt').write(configtoml) + + for overrides, expected in pass_tests: + cfg = config.Config('.', overrides=overrides) + assert dicts_equal(cfg, expected) + + for overrides in fail_tests: + with pytest.raises(ValueError): + config.Config('.', overrides=overrides) + + +def test_Config_access(): + pass -- GitLab