]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
pybind/mgr/smb: add unit tests file tests/test_resourcelib.py
authorJohn Mulligan <jmulligan@redhat.com>
Tue, 30 Jan 2024 19:33:29 +0000 (14:33 -0500)
committerJohn Mulligan <jmulligan@redhat.com>
Thu, 25 Apr 2024 23:10:39 +0000 (19:10 -0400)
Signed-off-by: John Mulligan <jmulligan@redhat.com>
src/pybind/mgr/smb/tests/test_resourcelib.py [new file with mode: 0644]

diff --git a/src/pybind/mgr/smb/tests/test_resourcelib.py b/src/pybind/mgr/smb/tests/test_resourcelib.py
new file mode 100644 (file)
index 0000000..0878fa7
--- /dev/null
@@ -0,0 +1,454 @@
+from typing import Dict, List, Optional, Tuple
+
+import dataclasses
+import enum
+
+import pytest
+
+import smb.resourcelib
+
+
+class Thingy(str, enum.Enum):
+    STUFF = 'stuff'
+    JUNK = 'junk'
+    DEBRIS = 'debris'
+
+
+@dataclasses.dataclass
+class ReadMe:
+    foo: str
+    bar: int = 0
+    baz: float = 0.1
+    bingo: Optional[str] = None
+    quux: Optional[List[int]] = None
+    womble: Optional[Dict[str, str]] = None
+    waver: Optional[Tuple[int, str]] = None
+
+
+def test_resource_config_field_metadata():
+    rmc = smb.resourcelib.Resource.create(ReadMe)
+    assert not rmc.conditional
+    assert rmc.type_name() == 'ReadMe'
+
+    # did it load the fields
+    assert 'foo' in rmc.fields
+    assert 'womble' in rmc.fields
+    assert len(rmc.fields) == 7
+
+    # magic getattr fields (quiet is always unset by default)
+    assert not rmc.foo.quiet
+    # optional
+    assert not rmc.foo.optional()
+    assert not rmc.bar.optional()
+    assert rmc.bingo.optional()
+    assert rmc.womble.optional()
+    # inner type
+    assert rmc.foo.inner_type() == str
+    assert rmc.bingo.inner_type() == str
+    # takes
+    assert not rmc.foo.takes(str)  # takes only useful for container types
+    assert rmc.quux.takes(list)
+    assert rmc.womble.takes(dict)
+    assert rmc.waver.takes(tuple)
+    # list element type
+    assert rmc.quux.list_element_type() == int
+    # dict element types
+    assert rmc.womble.dict_element_types() == (str, str)
+
+    assert 'ReadMe' in repr(rmc)
+
+
+@pytest.mark.parametrize(
+    "params",
+    [
+        # very basic
+        {
+            'kwargs': {'foo': 'smile'},
+            'expected': {
+                'foo': 'smile',
+                'bar': 0,
+                'baz': 0.1,
+            },
+        },
+        # set some other scalar values
+        {
+            'kwargs': {'foo': 'smile', 'bar': 12, 'bingo': 'b18'},
+            'expected': {
+                'foo': 'smile',
+                'bar': 12,
+                'baz': 0.1,
+                'bingo': 'b18',
+            },
+        },
+        # a list value
+        {
+            'kwargs': {'foo': 'smile', 'bar': 12, 'quux': [3, 11]},
+            'expected': {
+                'foo': 'smile',
+                'bar': 12,
+                'baz': 0.1,
+                'quux': [3, 11],
+            },
+        },
+        # a dict value
+        {
+            'kwargs': {
+                'foo': 'smile',
+                'bar': 12,
+                'womble': {"test": "one", "be": "good"},
+            },
+            'expected': {
+                'foo': 'smile',
+                'bar': 12,
+                'baz': 0.1,
+                'womble': {"test": "one", "be": "good"},
+            },
+        },
+    ],
+)
+def test_basic_resource_config_to_simplified(params):
+    rmc = smb.resourcelib.Resource.create(ReadMe)
+    obj = ReadMe(*params.get('args', []), **params.get('kwargs', {}))
+    result = rmc.object_to_simplified(obj)
+    assert result == params['expected']
+
+
+@pytest.mark.parametrize(
+    "params",
+    [
+        # very basic
+        {
+            "data": {
+                "foo": "hello",
+            },
+            "expected": ReadMe("hello"),
+        },
+        # two params
+        {
+            "data": {
+                "foo": "greetings",
+                "bar": 99,
+            },
+            "expected": ReadMe("greetings", bar=99),
+        },
+        # all scalars
+        {
+            "data": {
+                "foo": "aloha",
+                "bar": 101,
+                "baz": 3.14,
+                "bingo": "nameo",
+            },
+            "expected": ReadMe("aloha", bar=101, baz=3.14, bingo='nameo'),
+        },
+        # list and dict
+        {
+            "data": {
+                "foo": "icu",
+                "bar": 16,
+                "baz": 2.2,
+                "bingo": "yep",
+                "quux": [1, 5, 9],
+                "womble": {"something": "for everyone", "blank": ""},
+            },
+            "expected": ReadMe(
+                "icu",
+                bar=16,
+                baz=2.2,
+                bingo='yep',
+                quux=[1, 5, 9],
+                womble={"something": "for everyone", "blank": ""},
+            ),
+        },
+    ],
+)
+def test_basic_resource_config_from_simplified(params):
+    data = params['data']
+    rmc = smb.resourcelib.Resource.create(ReadMe)
+    result = rmc.object_from_simplified(data)
+    assert result == params['expected']
+
+
+def test_registry():
+    r = smb.resourcelib.Registry()
+    assert not r.resources
+    assert not r.types
+
+    @dataclasses.dataclass
+    class Foo:
+        name: str
+
+    resource = r.enable(Foo)
+    assert not r.resources
+    assert r.types
+
+    r.track('foo', resource)
+    assert r.resources
+    assert r.types
+
+    config2 = r.select({'resource_type': 'foo'})
+    assert config2 is resource
+
+    with pytest.raises(smb.resourcelib.MissingResourceTypeError):
+        r.select({})
+
+    with pytest.raises(smb.resourcelib.InvalidResourceTypeError):
+        r.select({'resource_type': 'oopsie'})
+
+    with pytest.raises(smb.resourcelib.ResourceTypeError):
+        cx = r.enable(ReadMe)
+        r.track('foo', cx)
+
+
+def test_registry_select_on_condition():
+    r = smb.resourcelib.Registry()
+
+    @dataclasses.dataclass
+    class A:
+        name: str
+
+        @staticmethod
+        def _condition(d):
+            return 'flavor' not in d and 'name' in d
+
+    @dataclasses.dataclass
+    class B:
+        name: str
+        flavor: str
+
+        @staticmethod
+        def _condition(d):
+            return 'flavor' in d
+
+    configa = r.enable(A)
+    configa.on_condition(A._condition)
+    configb = r.enable(B)
+    configb.on_condition(B._condition)
+
+    r.track('x', configa)
+    r.track('x', configb)
+
+    c = r.select({'resource_type': 'x', 'name': "joe", "flavor": "coffee"})
+    assert c is configb
+
+    c = r.select({'resource_type': 'x', 'name': "joe"})
+    assert c is configa
+
+    with pytest.raises(smb.resourcelib.ResourceTypeError):
+        r.select({'resource_type': 'x'})
+
+    # this should normally be impossible
+    configa._on_condition = None
+    configb._on_condition = None
+    with pytest.raises(smb.resourcelib.ResourceTypeError):
+        r.select({'resource_type': 'x'})
+
+
+@smb.resourcelib.component()
+class Worker:
+    name: str
+    age: int
+    role: Optional[str] = None
+
+    def validate(self):
+        if not self.name:
+            raise ValueError('name missing')
+        if self.age <= 0:
+            raise ValueError('invalid age')
+
+
+@smb.resourcelib.component()
+class Unit:
+    label: str
+    manager: Worker
+    full_timers: List[Worker]
+    interns: Optional[List[Worker]] = None
+
+
+@smb.resourcelib.resource('bigbiz')
+class BigBiz:
+    name: str
+    address: List[str]
+    ceo: Worker
+    units: Optional[List[Unit]] = None
+
+
+@smb.resourcelib.resource('smallbiz')
+class SmallBiz:
+    name: str
+    address: List[str]
+    people: List[Worker]
+
+
+def test_resource_round_trip():
+    r1 = BigBiz(
+        name='Mega Co',
+        address=['1010 Bigness Way', 'Metropolis', 'IL', '012345'],
+        ceo=Worker('F. Smith', 55),
+        units=[
+            Unit(
+                label='Sales',
+                manager=Worker('Al Pha', 42),
+                full_timers=[Worker('P. Rep', 33)],
+            ),
+            Unit(
+                label='Engineering',
+                manager=Worker('O. Mega', 42),
+                full_timers=[
+                    Worker('I. Contrib', 28),
+                    Worker('U. Needme', 29, role='QA'),
+                ],
+                interns=[Worker('J. Younya', 22)],
+            ),
+        ],
+    )
+
+    data = r1.to_simplified()
+    assert 'resource_type' in data
+
+    r2 = BigBiz._resource_config.object_from_simplified(data)
+    assert r1 == r2
+
+
+def test_resource_round_trip2():
+    r1 = SmallBiz(
+        name='Joes Diner',
+        address=['123 Main St', 'Smallville', 'IA', '048394'],
+        people=[
+            Worker('Joe', 44),
+            Worker('Lisa', 43),
+            Worker('Tina', 23),
+        ],
+    )
+
+    data = r1.to_simplified()
+    assert 'resource_type' in data
+
+    r2 = SmallBiz._resource_config.object_from_simplified(data)
+    assert r1 == r2
+
+
+@pytest.mark.parametrize(
+    "params",
+    [
+        # small biz 1
+        {
+            'data': {
+                'resource_type': 'smallbiz',
+                'name': 'Le Shoppe',
+                'address': ['12 Fashion Way', 'Urbia', 'WA', '01209'],
+                'people': [
+                    {'name': 'Madelyn', 'age': 39},
+                    {'name': 'Mark', 'age': 39},
+                ],
+            },
+            'expect_types': [SmallBiz],
+        },
+        # big biz 1
+        {
+            'data': {
+                'resource_type': 'bigbiz',
+                'name': 'MegaLoMart',
+                'address': ['1 MegaLo Drive', 'Mango', 'TX', '22020'],
+                'ceo': {'name': 'D. B. Bawes', 'age': 61},
+            },
+            'expect_types': [BigBiz],
+        },
+        # raw list
+        {
+            'data': [
+                {
+                    'resource_type': 'smallbiz',
+                    'name': 'Le Shoppe',
+                    'address': ['12 Fashion Way', 'Urbia', 'WA', '01209'],
+                    'people': [
+                        {'name': 'Madelyn', 'age': 39},
+                        {'name': 'Mark', 'age': 39},
+                    ],
+                },
+                {
+                    'resource_type': 'bigbiz',
+                    'name': 'MegaLoMart',
+                    'address': ['1 MegaLo Drive', 'Mango', 'TX', '22020'],
+                    'ceo': {'name': 'D. B. Bawes', 'age': 61},
+                },
+            ],
+            'expect_types': [SmallBiz, BigBiz],
+        },
+        # list object
+        {
+            'data': {
+                'resources': [
+                    {
+                        'resource_type': 'smallbiz',
+                        'name': 'Le Shoppe',
+                        'address': ['12 Fashion Way', 'Urbia', 'WA', '01209'],
+                        'people': [
+                            {'name': 'Madelyn', 'age': 39},
+                            {'name': 'Mark', 'age': 39},
+                        ],
+                    },
+                    {
+                        'resource_type': 'bigbiz',
+                        'name': 'MegaLoMart',
+                        'address': ['1 MegaLo Drive', 'Mango', 'TX', '22020'],
+                        'ceo': {'name': 'D. B. Bawes', 'age': 61},
+                    },
+                ]
+            },
+            'expect_types': [SmallBiz, BigBiz],
+        },
+    ],
+)
+def test_load(params):
+    data = params['data']
+    objs = smb.resourcelib.load(data)
+    assert len(objs) == len(params['expect_types'])
+    for obj, expect_type in zip(objs, params['expect_types']):
+        assert isinstance(obj, expect_type)
+
+
+def test_load_validation_error():
+    data = {
+        'resource_type': 'smallbiz',
+        'name': 'Le Shoppe',
+        'address': ['12 Fashion Way', 'Urbia', 'WA', '01209'],
+        'people': [
+            {'name': 'Madelyn', 'age': 39},
+            {'name': '', 'age': 39},
+        ],
+    }
+    with pytest.raises(ValueError):
+        smb.resourcelib.load(data)
+
+
+def test_missing_field_error():
+    data = {
+        'resource_type': 'smallbiz',
+        'name': 'Le Shoppe',
+        'address': ['12 Fashion Way', 'Urbia', 'WA', '01209'],
+        'people': [
+            {'name': 'Madelyn', 'age': 39},
+            {'age': 39},
+        ],
+    }
+    with pytest.raises(smb.resourcelib.MissingRequiredFieldError):
+        smb.resourcelib.load(data)
+
+
+def test_load_invalid_resources_type():
+    data = {'resources': 55}
+    with pytest.raises(TypeError):
+        smb.resourcelib.load(data)
+
+
+def test_explicit_none_in_data():
+    data = {
+        'resource_type': 'bigbiz',
+        'name': 'MegaLoMart',
+        'address': ['1 MegaLo Drive', 'Mango', 'TX', '22020'],
+        'ceo': {'name': 'D. B. Bawes', 'age': 61},
+        'units': None,
+    }
+    obj = BigBiz._resource_config.object_from_simplified(data)
+    assert obj.units is None