pytalloc: Add new BaseObject
authorAndrew Bartlett <abartlet@samba.org>
Mon, 22 Feb 2016 01:02:28 +0000 (14:02 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 8 Mar 2016 00:58:26 +0000 (01:58 +0100)
This new object not only avoids the ABI issues of talloc.Object
it stores one more pointer, being the start of the array, and
so can be used to fix the PIDL bindings/talloc refcount issue.

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
lib/talloc/pytalloc.c
lib/talloc/pytalloc.h
lib/talloc/pytalloc_guide.txt
lib/talloc/pytalloc_private.h [new file with mode: 0644]
lib/talloc/pytalloc_util.c
lib/talloc/test_pytalloc.c
lib/talloc/test_pytalloc.py

index 3afae9ce012bffae82e1170544c98e9d5e97f394..74349da843dfbf1df5334bf47ab20cc12f748dd7 100644 (file)
@@ -20,6 +20,7 @@
 #include <Python.h>
 #include <talloc.h>
 #include <pytalloc.h>
+#include "pytalloc_private.h"
 
 static PyTypeObject TallocObject_Type;
 
@@ -157,6 +158,86 @@ static PyTypeObject TallocObject_Type = {
 #endif
 };
 
+/**
+ * Default (but only slightly more useful than the default) implementation of Repr().
+ */
+static PyObject *pytalloc_base_default_repr(PyObject *obj)
+{
+       pytalloc_BaseObject *talloc_obj = (pytalloc_BaseObject *)obj;
+       PyTypeObject *type = (PyTypeObject*)PyObject_Type(obj);
+
+       return PyStr_FromFormat("<%s talloc based object at 0x%p>",
+                               type->tp_name, talloc_obj->ptr);
+}
+
+/**
+ * Simple dealloc for talloc-wrapping PyObjects
+ */
+static void pytalloc_base_dealloc(PyObject* self)
+{
+       pytalloc_BaseObject *obj = (pytalloc_BaseObject *)self;
+       assert(talloc_unlink(NULL, obj->talloc_ctx) != -1);
+       obj->talloc_ctx = NULL;
+       self->ob_type->tp_free(self);
+}
+
+/**
+ * Default (but only slightly more useful than the default) implementation of cmp.
+ */
+#if PY_MAJOR_VERSION >= 3
+static PyObject *pytalloc_base_default_richcmp(PyObject *obj1, PyObject *obj2, int op)
+{
+       void *ptr1;
+       void *ptr2;
+       if (Py_TYPE(obj1) == Py_TYPE(obj2)) {
+               /* When types match, compare pointers */
+               ptr1 = pytalloc_get_ptr(obj1);
+               ptr2 = pytalloc_get_ptr(obj2);
+       } else if (PyObject_TypeCheck(obj2, &TallocObject_Type)) {
+               /* Otherwise, compare types */
+               ptr1 = Py_TYPE(obj1);
+               ptr2 = Py_TYPE(obj2);
+       } else {
+               Py_INCREF(Py_NotImplemented);
+               return Py_NotImplemented;
+       }
+       switch (op) {
+               case Py_EQ: return PyBool_FromLong(ptr1 == ptr2);
+               case Py_NE: return PyBool_FromLong(ptr1 != ptr2);
+               case Py_LT: return PyBool_FromLong(ptr1 < ptr2);
+               case Py_GT: return PyBool_FromLong(ptr1 > ptr2);
+               case Py_LE: return PyBool_FromLong(ptr1 <= ptr2);
+               case Py_GE: return PyBool_FromLong(ptr1 >= ptr2);
+       }
+       Py_INCREF(Py_NotImplemented);
+       return Py_NotImplemented;
+}
+#else
+static int pytalloc_base_default_cmp(PyObject *_obj1, PyObject *_obj2)
+{
+       pytalloc_BaseObject *obj1 = (pytalloc_BaseObject *)_obj1,
+                                        *obj2 = (pytalloc_BaseObject *)_obj2;
+       if (obj1->ob_type != obj2->ob_type)
+               return ((char *)obj1->ob_type - (char *)obj2->ob_type);
+
+       return ((char *)pytalloc_get_ptr(obj1) - (char *)pytalloc_get_ptr(obj2));
+}
+#endif
+
+static PyTypeObject TallocBaseObject_Type = {
+       .tp_name = "talloc.BaseObject",
+       .tp_doc = "Python wrapper for a talloc-maintained object.",
+       .tp_basicsize = sizeof(pytalloc_BaseObject),
+       .tp_dealloc = (destructor)pytalloc_base_dealloc,
+       .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+       .tp_repr = pytalloc_base_default_repr,
+#if PY_MAJOR_VERSION >= 3
+       .tp_richcompare = pytalloc_base_default_richcmp,
+#else
+       .tp_compare = pytalloc_base_default_cmp,
+#endif
+};
+
 #define MODULE_DOC PyDoc_STR("Python wrapping of talloc-maintained objects.")
 
 #if PY_MAJOR_VERSION >= 3
@@ -177,6 +258,9 @@ static PyObject *module_init(void)
        if (PyType_Ready(&TallocObject_Type) < 0)
                return NULL;
 
+       if (PyType_Ready(&TallocBaseObject_Type) < 0)
+               return NULL;
+
 #if PY_MAJOR_VERSION >= 3
        m = PyModule_Create(&moduledef);
 #else
@@ -187,6 +271,8 @@ static PyObject *module_init(void)
 
        Py_INCREF(&TallocObject_Type);
        PyModule_AddObject(m, "Object", (PyObject *)&TallocObject_Type);
+       Py_INCREF(&TallocBaseObject_Type);
+       PyModule_AddObject(m, "BaseObject", (PyObject *)&TallocBaseObject_Type);
        return m;
 }
 
index 110445a0ab804cc44df60dcf207083d7110bb287..3c1cfda259de8c9325f0e61e4321f864a3c3c8b2 100644 (file)
 typedef struct {
        PyObject_HEAD
        TALLOC_CTX *talloc_ctx;
-       void *ptr;
+       void *ptr; /* eg the array element */
 } pytalloc_Object;
 
 /* Return the PyTypeObject for pytalloc_Object. Returns a new reference. */
 PyTypeObject *pytalloc_GetObjectType(void);
 
+/* Return the PyTypeObject for pytalloc_BaseObject. Returns a new reference. */
+PyTypeObject *pytalloc_GetBaseObjectType(void);
+
 /* Check whether a specific object is a talloc Object. */
 int pytalloc_Check(PyObject *);
 
+int pytalloc_BaseObject_check(PyObject *);
+
 /* Retrieve the pointer for a pytalloc_object. Like talloc_get_type() 
  * but for pytalloc_Objects. */
 void *_pytalloc_get_type(PyObject *py_obj, const char *type_name);
@@ -56,4 +61,7 @@ PyObject *pytalloc_reference_ex(PyTypeObject *py_type, TALLOC_CTX *mem_ctx, void
 PyObject *pytalloc_CObject_FromTallocPtr(void *);
 #endif
 
+size_t pytalloc_BaseObject_size(void);
+
+
 #endif /* _PYTALLOC_H_ */
index 36ae5ffe053bdf83f8f2808bbc1355e139ee55e8..b0351bb9eb0389e00243ff7e711a7830c6fe5127 100644 (file)
@@ -29,7 +29,7 @@ Python's PEP3149 ABI tag, for example "pytalloc.cpython34m".
 To make a build for Python 3, configure with PYTHON=/usr/bin/python3.
 .
 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-pytalloc_Object
+pytalloc_Object / pytalloc_BaseObject
 
 This is the new base class that all Python objects that wrap talloc pointers
 derive from. It is itself a subclass of the "Object" type that all objects
@@ -53,6 +53,10 @@ compares the pointers the object is wrapping rather than the objects
 themselves (since there can be multiple objects that wrap the same talloc
 pointer).
 
+It is preferred to use pytalloc_BaseObject as this implementation
+exposes less in the C ABI and correctly supports pointers in C arrays
+in the way needed by PIDL.
+
 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 PyTypeObject *pytalloc_GetObjectType(void)
 
@@ -60,12 +64,25 @@ Obtain a reference to the PyTypeObject for `pytalloc_Object`. The reference
 counter for the object will be incremented, so the caller will have to
 decrement it when it no longer needs it (using `Py_DECREF`).
 
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+PyTypeObject *pytalloc_GetBaseObjectType(void)
+
+Obtain a reference to the PyTypeObject for `pytalloc_Object`. The reference
+counter for the object will be incremented, so the caller will have to
+decrement it when it no longer needs it (using `Py_DECREF`).
+
 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-
 int pytalloc_Check(PyObject *)
 
 Check whether a specific object is a talloc Object. Returns non-zero if it is
 a pytalloc_Object and zero otherwise.
 
+=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-
+int pytalloc_BaseObject_Check(PyObject *)
+
+Check whether a specific object is a talloc BaseObject. Returns non-zero if it is
+a pytalloc_BaseObject and zero otherwise.
+
 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 type *pytalloc_get_type(PyObject *py_obj, type)
 
@@ -75,13 +92,14 @@ C type, similar to a type passed to `talloc_get_type`.
 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 pytalloc_get_ptr(PyObject *py_obj)
 
-Retrieve the pointer from a `pytalloc_Object` py_obj. There is no
-type checking - use `pytalloc_get_type` if possible.
+Retrieve the pointer from a `pytalloc_Object` or `pytalloc_BaseObject`
+py_obj. There is no type checking - use `pytalloc_get_type` if
+possible.
 
 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 TALLOC_CTX *pytalloc_get_mem_ctx(PyObject *py_obj)
 
-Retrieve the talloc context associated with a pytalloc_Object.
+Retrieve the talloc context associated with a pytalloc_Object or pytalloc_BaseObject.
 
 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 PyObject *pytalloc_steal_ex(PyTypeObject *py_type, TALLOC_CTX *mem_ctx, void *ptr)
diff --git a/lib/talloc/pytalloc_private.h b/lib/talloc/pytalloc_private.h
new file mode 100644 (file)
index 0000000..b23cdfc
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+   Unix SMB/CIFS implementation.
+   Samba utility functions
+   Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2016
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+typedef struct {
+       PyObject_HEAD
+       TALLOC_CTX *talloc_ctx;
+       TALLOC_CTX *talloc_ptr_ctx; /* eg the start of the array */
+       void *ptr; /* eg the array element */
+} pytalloc_BaseObject;
index b6e5f0f45a2219c404193f96b663715723922054..307e38a4c1e921403727158439e3d0e551b550a4 100644 (file)
@@ -22,6 +22,7 @@
 #include <talloc.h>
 #include "pytalloc.h"
 #include <assert.h>
+#include "pytalloc_private.h"
 
 _PUBLIC_ PyTypeObject *pytalloc_GetObjectType(void)
 {
@@ -43,23 +44,82 @@ _PUBLIC_ PyTypeObject *pytalloc_GetObjectType(void)
        return type;
 }
 
+_PUBLIC_ PyTypeObject *pytalloc_GetBaseObjectType(void)
+{
+       static PyTypeObject *type = NULL;
+       PyObject *mod;
+
+       if (type != NULL) {
+               return type;
+       }
+
+       mod = PyImport_ImportModule("talloc");
+       if (mod == NULL) {
+               return NULL;
+       }
+
+       type = (PyTypeObject *)PyObject_GetAttrString(mod, "BaseObject");
+       Py_DECREF(mod);
+
+       return type;
+}
+
 /**
  * Import an existing talloc pointer into a Python object.
  */
 _PUBLIC_ PyObject *pytalloc_steal_ex(PyTypeObject *py_type, TALLOC_CTX *mem_ctx,
-                                                  void *ptr)
+                                    void *ptr)
 {
-       pytalloc_Object *ret = (pytalloc_Object *)py_type->tp_alloc(py_type, 0);
-       ret->talloc_ctx = talloc_new(NULL);
-       if (ret->talloc_ctx == NULL) {
-               return NULL;
+       PyTypeObject *BaseObjectType = pytalloc_GetBaseObjectType();
+       PyTypeObject *ObjectType = pytalloc_GetObjectType();
+
+       if (mem_ctx == NULL) {
+               return PyErr_NoMemory();
        }
-       if (talloc_steal(ret->talloc_ctx, mem_ctx) == NULL) {
+
+       if (PyType_IsSubtype(py_type, BaseObjectType)) {
+               pytalloc_BaseObject *ret
+                       = (pytalloc_BaseObject *)py_type->tp_alloc(py_type, 0);
+
+               ret->talloc_ctx = talloc_new(NULL);
+               if (ret->talloc_ctx == NULL) {
+                       return NULL;
+               }
+
+               /*
+                * This allows us to keep multiple references to this object -
+                * we only reference this context, which is per ptr, not the
+                * talloc_ctx, which is per pytalloc_Object
+                */
+               if (talloc_steal(ret->talloc_ctx, mem_ctx) == NULL) {
+                       return NULL;
+               }
+               ret->talloc_ptr_ctx = mem_ctx;
+               talloc_set_name_const(ret->talloc_ctx, py_type->tp_name);
+               ret->ptr = ptr;
+               return (PyObject *)ret;
+
+       } else if (PyType_IsSubtype(py_type, ObjectType)) {
+               pytalloc_Object *ret
+                       = (pytalloc_Object *)py_type->tp_alloc(py_type, 0);
+
+               ret->talloc_ctx = talloc_new(NULL);
+               if (ret->talloc_ctx == NULL) {
+                       return NULL;
+               }
+
+               if (talloc_steal(ret->talloc_ctx, mem_ctx) == NULL) {
+                       return NULL;
+               }
+               talloc_set_name_const(ret->talloc_ctx, py_type->tp_name);
+               ret->ptr = ptr;
+               return (PyObject *)ret;
+       } else {
+               PyErr_SetString(PyExc_RuntimeError,
+                               "pytalloc_steal_ex() called for object type "
+                               "not based on talloc");
                return NULL;
        }
-       talloc_set_name_const(ret->talloc_ctx, py_type->tp_name);
-       ret->ptr = ptr;
-       return (PyObject *)ret;
 }
 
 /**
@@ -74,27 +134,57 @@ _PUBLIC_ PyObject *pytalloc_steal(PyTypeObject *py_type, void *ptr)
 /**
  * Import an existing talloc pointer into a Python object, leaving the
  * original parent, and creating a reference to the object in the python
- * object
+ * object.
+ *
+ * We remember the object we hold the reference to (a
+ * possibly-non-talloc pointer), the existing parent (typically the
+ * start of the array) and the new referenced parent.  That way we can
+ * cope with the fact that we will have multiple parents, one per time
+ * python sees the object.
  */
-_PUBLIC_ PyObject *pytalloc_reference_ex(PyTypeObject *py_type, TALLOC_CTX *mem_ctx, void *ptr)
+_PUBLIC_ PyObject *pytalloc_reference_ex(PyTypeObject *py_type,
+                                        TALLOC_CTX *mem_ctx, void *ptr)
 {
-       pytalloc_Object *ret;
+       PyTypeObject *BaseObjectType = pytalloc_GetBaseObjectType();
+       PyTypeObject *ObjectType = pytalloc_GetObjectType();
 
-       if (ptr == NULL) {
-               Py_RETURN_NONE;
+       if (mem_ctx == NULL) {
+               return PyErr_NoMemory();
        }
 
-       ret = (pytalloc_Object *)py_type->tp_alloc(py_type, 0);
-       ret->talloc_ctx = talloc_new(NULL);
-       if (ret->talloc_ctx == NULL) {
-               return NULL;
-       }
-       if (talloc_reference(ret->talloc_ctx, mem_ctx) == NULL) {
+       if (PyType_IsSubtype(py_type, BaseObjectType)) {
+               pytalloc_BaseObject *ret
+                       = (pytalloc_BaseObject *)py_type->tp_alloc(py_type, 0);
+               ret->talloc_ctx = talloc_new(NULL);
+               if (ret->talloc_ctx == NULL) {
+                       return NULL;
+               }
+               if (talloc_reference(ret->talloc_ctx, mem_ctx) == NULL) {
+                       return NULL;
+               }
+               talloc_set_name_const(ret->talloc_ctx, py_type->tp_name);
+               ret->talloc_ptr_ctx = mem_ctx;
+               ret->ptr = ptr;
+               return (PyObject *)ret;
+       } else if (PyType_IsSubtype(py_type, ObjectType)) {
+               pytalloc_Object *ret
+                       = (pytalloc_Object *)py_type->tp_alloc(py_type, 0);
+               ret->talloc_ctx = talloc_new(NULL);
+               if (ret->talloc_ctx == NULL) {
+                       return NULL;
+               }
+               if (talloc_reference(ret->talloc_ctx, mem_ctx) == NULL) {
+                       return NULL;
+               }
+               talloc_set_name_const(ret->talloc_ctx, py_type->tp_name);
+               ret->ptr = ptr;
+               return (PyObject *)ret;
+       } else {
+               PyErr_SetString(PyExc_RuntimeError,
+                               "pytalloc_reference_ex() called for object type "
+                               "not based on talloc");
                return NULL;
        }
-       talloc_set_name_const(ret->talloc_ctx, py_type->tp_name);
-       ret->ptr = ptr;
-       return (PyObject *)ret;
 }
 
 #if PY_MAJOR_VERSION < 3
@@ -121,6 +211,18 @@ _PUBLIC_ int pytalloc_Check(PyObject *obj)
        return PyObject_TypeCheck(obj, tp);
 }
 
+_PUBLIC_ int pytalloc_BaseObject_check(PyObject *obj)
+{
+       PyTypeObject *tp = pytalloc_GetBaseObjectType();
+
+       return PyObject_TypeCheck(obj, tp);
+}
+
+_PUBLIC_ size_t pytalloc_BaseObject_size(void)
+{
+       return sizeof(pytalloc_BaseObject);
+}
+
 _PUBLIC_ void *_pytalloc_get_type(PyObject *py_obj, const char *type_name)
 {
        void *ptr = _pytalloc_get_ptr(py_obj);
@@ -138,10 +240,22 @@ _PUBLIC_ void *_pytalloc_get_type(PyObject *py_obj, const char *type_name)
 
 _PUBLIC_ void *_pytalloc_get_ptr(PyObject *py_obj)
 {
-       return ((pytalloc_Object *)py_obj)->ptr;
+       if (pytalloc_BaseObject_check(py_obj)) {
+               return ((pytalloc_BaseObject *)py_obj)->ptr;
+       }
+       if (pytalloc_Check(py_obj)) {
+               return ((pytalloc_Object *)py_obj)->ptr;
+       }
+       return NULL;
 }
 
 _PUBLIC_ TALLOC_CTX *_pytalloc_get_mem_ctx(PyObject *py_obj)
 {
-       return ((pytalloc_Object *)py_obj)->talloc_ctx;
+       if (pytalloc_BaseObject_check(py_obj)) {
+               return ((pytalloc_BaseObject *)py_obj)->talloc_ptr_ctx;
+       }
+       if (pytalloc_Check(py_obj)) {
+               return ((pytalloc_Object *)py_obj)->talloc_ctx;
+       }
+       return NULL;
 }
index 0097cda8cf3282f30161c3b65dea89ee5011e572..a7c31c1ad5ead882d0f66832f269cb5e369c5ab8 100644 (file)
@@ -42,6 +42,18 @@ static PyObject *testpytalloc_get_object_type(PyObject *mod) {
        return type;
 }
 
+static PyObject *testpytalloc_base_new(PyTypeObject *mod)
+{
+       char *obj = talloc_strdup(NULL, "This is a test string for a BaseObject");;
+       return pytalloc_steal(pytalloc_GetBaseObjectType(), obj);
+}
+
+static PyObject *testpytalloc_base_get_object_type(PyObject *mod) {
+       PyObject *type = (PyObject *)pytalloc_GetBaseObjectType();
+       Py_INCREF(type);
+       return type;
+}
+
 static PyObject *testpytalloc_reference(PyObject *mod, PyObject *args) {
        PyObject *source = NULL;
        void *ptr;
@@ -53,13 +65,30 @@ static PyObject *testpytalloc_reference(PyObject *mod, PyObject *args) {
        return pytalloc_reference_ex(pytalloc_GetObjectType(), ptr, ptr);
 }
 
+static PyObject *testpytalloc_base_reference(PyObject *mod, PyObject *args) {
+       PyObject *source = NULL;
+       void *mem_ctx;
+
+       if (!PyArg_ParseTuple(args, "O!", pytalloc_GetBaseObjectType(), &source)) {
+               return NULL;
+       }
+       mem_ctx = pytalloc_get_mem_ctx(source);
+       return pytalloc_reference_ex(pytalloc_GetBaseObjectType(), mem_ctx, mem_ctx);
+}
+
 static PyMethodDef test_talloc_methods[] = {
        { "new", (PyCFunction)testpytalloc_new, METH_NOARGS,
                "create a talloc Object with a testing string"},
        { "get_object_type", (PyCFunction)testpytalloc_get_object_type, METH_NOARGS,
                "call pytalloc_GetObjectType"},
+       { "base_new", (PyCFunction)testpytalloc_base_new, METH_NOARGS,
+               "create a talloc BaseObject with a testing string"},
+       { "base_get_object_type", (PyCFunction)testpytalloc_base_get_object_type, METH_NOARGS,
+               "call pytalloc_GetBaseObjectType"},
        { "reference", (PyCFunction)testpytalloc_reference, METH_VARARGS,
                "call pytalloc_reference_ex"},
+       { "base_reference", (PyCFunction)testpytalloc_base_reference, METH_VARARGS,
+               "call pytalloc_reference_ex"},
        { NULL }
 };
 
@@ -104,6 +133,46 @@ static PyTypeObject DObject_Type = {
        .tp_doc = "test talloc object that calls a function when underlying data is freed\n",
 };
 
+static PyTypeObject DBaseObject_Type;
+
+static int d_base_object_destructor(void *ptr)
+{
+       PyObject *destructor_func = *talloc_get_type(ptr, PyObject*);
+       PyObject *ret;
+       ret = PyObject_CallObject(destructor_func, NULL);
+       Py_DECREF(destructor_func);
+       if (ret == NULL) {
+               PyErr_Print();
+       } else {
+               Py_DECREF(ret);
+       }
+       return 0;
+}
+
+static PyObject *d_base_object_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+       PyObject *destructor_func = NULL;
+       PyObject **obj;
+
+       if (!PyArg_ParseTuple(args, "O", &destructor_func))
+               return NULL;
+       Py_INCREF(destructor_func);
+
+       obj = talloc(NULL, PyObject*);
+       *obj = destructor_func;
+
+       talloc_set_destructor((void*)obj, d_base_object_destructor);
+       return pytalloc_steal(&DBaseObject_Type, obj);
+}
+
+static PyTypeObject DBaseObject_Type = {
+       .tp_name = "_test_pytalloc.DBaseObject",
+       .tp_methods = NULL,
+       .tp_new = d_base_object_new,
+       .tp_flags = Py_TPFLAGS_DEFAULT,
+       .tp_doc = "test talloc object that calls a function when underlying data is freed\n",
+};
+
 #define MODULE_DOC PyDoc_STR("Test utility module for pytalloc")
 
 #if PY_MAJOR_VERSION >= 3
@@ -126,6 +195,12 @@ static PyObject *module_init(void)
                return NULL;
        }
 
+       DBaseObject_Type.tp_basicsize = pytalloc_BaseObject_size();
+       DBaseObject_Type.tp_base = pytalloc_GetBaseObjectType();
+       if (PyType_Ready(&DBaseObject_Type) < 0) {
+               return NULL;
+       }
+
 #if PY_MAJOR_VERSION >= 3
        m = PyModule_Create(&moduledef);
 #else
@@ -140,6 +215,10 @@ static PyObject *module_init(void)
        Py_INCREF(DObject_Type.tp_base);
        PyModule_AddObject(m, "DObject", (PyObject *)&DObject_Type);
 
+       Py_INCREF(&DBaseObject_Type);
+       Py_INCREF(DBaseObject_Type.tp_base);
+       PyModule_AddObject(m, "DBaseObject", (PyObject *)&DBaseObject_Type);
+
        return m;
 }
 
index a6133735a98df8b531dbb7c3b5413a3a8d155a46..2e58d28c7e2202754812da67885bc0073c83ed25 100644 (file)
@@ -43,6 +43,12 @@ class TallocTests(unittest.TestCase):
         self.assertTrue(repr(obj).startswith(prefix))
         self.assertEqual(repr(obj), str(obj))
 
+    def test_base_repr(self):
+        obj = _test_pytalloc.base_new()
+        prefix = '<talloc.BaseObject talloc based object at'
+        self.assertTrue(repr(obj).startswith(prefix))
+        self.assertEqual(repr(obj), str(obj))
+
     def test_destructor(self):
         # Check correct lifetime of the talloc'd data
         lst = []
@@ -52,6 +58,15 @@ class TallocTests(unittest.TestCase):
         gc.collect()
         self.assertEqual(lst, ['dead'])
 
+    def test_base_destructor(self):
+        # Check correct lifetime of the talloc'd data
+        lst = []
+        obj = _test_pytalloc.DBaseObject(lambda: lst.append('dead'))
+        self.assertEqual(lst, [])
+        del obj
+        gc.collect()
+        self.assertEqual(lst, ['dead'])
+
 
 class TallocComparisonTests(unittest.TestCase):
 
@@ -94,13 +109,54 @@ class TallocComparisonTests(unittest.TestCase):
         self.assertFalse(obj1 >= obj2)
         self.assertFalse(obj1 > obj2)
 
+class TallocBaseComparisonTests(unittest.TestCase):
+
+    def test_compare_same(self):
+        obj1 = _test_pytalloc.base_new()
+        self.assertTrue(obj1 == obj1)
+        self.assertFalse(obj1 != obj1)
+        self.assertTrue(obj1 <= obj1)
+        self.assertFalse(obj1 < obj1)
+        self.assertTrue(obj1 >= obj1)
+        self.assertFalse(obj1 > obj1)
+
+    def test_compare_different(self):
+        # object comparison is consistent
+        obj1, obj2 = sorted([
+            _test_pytalloc.base_new(),
+            _test_pytalloc.base_new()])
+        self.assertFalse(obj1 == obj2)
+        self.assertTrue(obj1 != obj2)
+        self.assertTrue(obj1 <= obj2)
+        self.assertTrue(obj1 < obj2)
+        self.assertFalse(obj1 >= obj2)
+        self.assertFalse(obj1 > obj2)
+
+    def test_compare_different_types(self):
+        # object comparison falls back to comparing types
+        if sys.version_info >= (3, 0):
+            # In Python 3, types are unorderable -- nothing to test
+            return
+        if talloc.BaseObject < _test_pytalloc.DBaseObject:
+            obj1 = _test_pytalloc.base_new()
+            obj2 = _test_pytalloc.DBaseObject(dummy_func)
+        else:
+            obj2 = _test_pytalloc.base_new()
+            obj1 = _test_pytalloc.DBaseObject(dummy_func)
+        self.assertFalse(obj1 == obj2)
+        self.assertTrue(obj1 != obj2)
+        self.assertTrue(obj1 <= obj2)
+        self.assertTrue(obj1 < obj2)
+        self.assertFalse(obj1 >= obj2)
+        self.assertFalse(obj1 > obj2)
+
 
 class TallocUtilTests(unittest.TestCase):
 
     def test_get_type(self):
         self.assertTrue(talloc.Object is _test_pytalloc.get_object_type())
 
-    def test_refrence(self):
+    def test_reference(self):
         # Check correct lifetime of the talloc'd data with multiple references
         lst = []
         obj = _test_pytalloc.DObject(lambda: lst.append('dead'))
@@ -112,6 +168,21 @@ class TallocUtilTests(unittest.TestCase):
         gc.collect()
         self.assertEqual(lst, ['dead'])
 
+    def test_get_base_type(self):
+        self.assertTrue(talloc.BaseObject is _test_pytalloc.base_get_object_type())
+
+    def test_base_reference(self):
+        # Check correct lifetime of the talloc'd data with multiple references
+        lst = []
+        obj = _test_pytalloc.DBaseObject(lambda: lst.append('dead'))
+        ref = _test_pytalloc.base_reference(obj)
+        del obj
+        gc.collect()
+        self.assertEqual(lst, [])
+        del ref
+        gc.collect()
+        self.assertEqual(lst, ['dead'])
+
 
 if __name__ == '__main__':
     unittest.TestProgram()