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