commit:     d8ccaafb718d547f8a0c6f55ad20eac1907db7d5
Author:     Brian Harring <ferringb <AT> gmail <DOT> com>
AuthorDate: Sun Dec 14 17:17:17 2025 +0000
Commit:     Brian Harring <ferringb <AT> gmail <DOT> com>
CommitDate: Mon Dec 15 12:42:10 2025 +0000
URL:        
https://gitweb.gentoo.org/proj/pkgcore/snakeoil.git/commit/?id=d8ccaafb

Add tests for get_attrs_of to detect when it's no longer needed.

vars() doesn't handle slotted classes in any way usable, so this
function addresses that.  This test just validates *why* this
function exists so we can know when to remove it, if ever.

Signed-off-by: Brian Harring <ferringb <AT> gmail.com>

 tests/klass/test_util.py | 140 +++++++++++++++++++++++++++--------------------
 1 file changed, 80 insertions(+), 60 deletions(-)

diff --git a/tests/klass/test_util.py b/tests/klass/test_util.py
index d33ec9a..ced1fb2 100644
--- a/tests/klass/test_util.py
+++ b/tests/klass/test_util.py
@@ -17,67 +17,87 @@ from snakeoil.klass.util import (
 )
 
 
-def test_get_attrs_of():
-    def _assert_no_duplicates(obj, **kwds):
-        seen = set()
-        for k, v in get_attrs_of(obj, **kwds):
-            assert k not in seen, "duplicate attributes must not be returned"
-            yield k, v
-            seen.add(k)
-
-    def assert_attrs(o, result, **kwds):
-        assert result == dict(_assert_no_duplicates(o, **kwds))
-
-    def mk_obj(parent=object, slots=None, create=False) -> Any:
-        class kls(parent):
-            if slots is not None:
-                locals()["__slots__"] = (
-                    (slots,) if isinstance(slots, str) else tuple(slots)
-                )
-
-        if not create:
-            return kls
-        obj = kls()
+class Test_get_attrs_of:
+    def test_reason_for_existing(self):
+        # This exists to detect when python fixes the vars() issue, rendering 
get_attrs_of redundant.
+        class foon:
+            __slots__ = ("x", "y")
+
+        inst = foon()
+        inst.x, inst.y = 1, 2
+        with pytest.raises(TypeError):
+            vars(inst)
+
+        class with_dict(foon): ...
+
+        inst = with_dict()
+        inst.x, inst.y = (1, 2)
+        assert [] == list(vars(inst))
+        # Push something into the __dict__
+        inst.z = 3  # pyright: ignore[reportAttributeAccessIssue]
+        assert ["z"] == list(vars(inst))
+
+    def test_it(self):
+        def _assert_no_duplicates(obj, **kwds):
+            seen = set()
+            for k, v in get_attrs_of(obj, **kwds):
+                assert k not in seen, "duplicate attributes must not be 
returned"
+                yield k, v
+                seen.add(k)
+
+        def assert_attrs(o, result, **kwds):
+            assert result == dict(_assert_no_duplicates(o, **kwds))
+
+        def mk_obj(parent=object, slots=None, create=False) -> Any:
+            class kls(parent):
+                if slots is not None:
+                    locals()["__slots__"] = (
+                        (slots,) if isinstance(slots, str) else tuple(slots)
+                    )
+
+            if not create:
+                return kls
+            obj = kls()
+            assert_attrs(obj, {})
+            return obj
+
+        # make a pure chained slotted class
+        obj = mk_obj(mk_obj(slots="x"), "y", create=True)
+        obj.x = 1
+        assert_attrs(obj, {"x": 1})
+        obj.y = 2
+        assert_attrs(obj, {"x": 1, "y": 2})
+        # verify the reverse direction out of paranoia.
+        del obj.x
+        assert_attrs(obj, {"y": 2})
+
+        # verify it handles shadowed slots
+        obj = mk_obj(mk_obj(slots="x"), slots="x", create=True)  # x is 
shadowed
+        obj.x = 1
+        assert_attrs(obj, {"x": 1})
+
+        # ...verify it handles layered __slots__ that normal visibility of the 
underlying
+        # slot, even if it's still a usable attribute
+        obj = mk_obj(mk_obj(slots="x"), slots=(), create=True)
+
+        # the fun one.  Mixed slotting.
+        obj = mk_obj(mk_obj(), slots="x", create=True)
+        obj.x = 1
+        assert "x" not in obj.__dict__, (
+            "a slotted variable was tucked into __dict__; this is not how 
python is understood to work for this code.  While real code can do this- it's 
dumb but possible- this test doesn't do that, thus something is off."
+        )
+        assert_attrs(obj, {"x": 1})
+        obj.y = 2
+        assert_attrs(obj, {"x": 1, "y": 2})
+
+        # check weakref and suppression
+        obj = mk_obj(create=True)
+        ref = weakref.ref(obj)
         assert_attrs(obj, {})
-        return obj
-
-    # make a pure chained slotted class
-    obj = mk_obj(mk_obj(slots="x"), "y", create=True)
-    obj.x = 1
-    assert_attrs(obj, {"x": 1})
-    obj.y = 2
-    assert_attrs(obj, {"x": 1, "y": 2})
-    # verify the reverse direction out of paranoia.
-    del obj.x
-    assert_attrs(obj, {"y": 2})
-
-    # verify it handles shadowed slots
-    obj = mk_obj(mk_obj(slots="x"), slots="x", create=True)  # x is shadowed
-    obj.x = 1
-    assert_attrs(obj, {"x": 1})
-
-    # ...verify it handles layered __slots__ that normal visibility of the 
underlying
-    # slot, even if it's still a usable attribute
-    obj = mk_obj(mk_obj(slots="x"), slots=(), create=True)
-
-    # the fun one.  Mixed slotting.
-    obj = mk_obj(mk_obj(), slots="x", create=True)
-    obj.x = 1
-    assert "x" not in obj.__dict__, (
-        "a slotted variable was tucked into __dict__; this is not how python 
is understood to work for this code.  While real code can do this- it's dumb 
but possible- this test doesn't do that, thus something is off."
-    )
-    assert_attrs(obj, {"x": 1})
-    obj.y = 2
-    assert_attrs(obj, {"x": 1, "y": 2})
-
-    # check weakref and suppression
-    obj = mk_obj(create=True)
-    ref = weakref.ref(obj)
-    assert_attrs(obj, {})
-
-    assert_attrs(obj, {"__weakref__": ref}, weakref=True)
-    obj.blah = 1
-    assert_attrs(obj, {}, suppressions=["blah"])
+
+        assert_attrs(obj, {"__weakref__": ref}, weakref=True)
+        obj.blah = 1
+        assert_attrs(obj, {}, suppressions=["blah"])
 
 
 def test_slots_of():

Reply via email to