pytalloc: Add tests
authorPetr Viktorin <pviktori@redhat.com>
Thu, 5 Mar 2015 09:06:05 +0000 (10:06 +0100)
committerJelmer Vernooij <jelmer@samba.org>
Thu, 23 Apr 2015 23:50:11 +0000 (01:50 +0200)
Add tests for pytalloc.

Since talloc objects can't be created from Python, a C extension
with helpers is added.

Signed-off-by: Petr Viktorin <pviktori@redhat.com>
Reviewed-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Jelmer Vernooij <jelmer@samba.org>
lib/talloc/test_pytalloc.c [new file with mode: 0644]
lib/talloc/test_pytalloc.py [new file with mode: 0644]
lib/talloc/wscript

diff --git a/lib/talloc/test_pytalloc.c b/lib/talloc/test_pytalloc.c
new file mode 100644 (file)
index 0000000..2eaa7c3
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+   Samba Unix SMB/CIFS implementation.
+
+   C utilities for the pytalloc test suite.
+   Provides the "_test_pytalloc" Python module.
+
+   NOTE: Please read talloc_guide.txt for full documentation
+
+   Copyright (C) Petr Viktorin 2015
+
+     ** NOTE! The following LGPL license applies to the talloc
+     ** library. This does NOT imply that all of Samba is released
+     ** under the LGPL
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 3 of the License, or (at your option) any later version.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <Python.h>
+#include <talloc.h>
+#include <pytalloc.h>
+
+static PyObject *testpytalloc_new(PyTypeObject *mod)
+{
+       char *obj = talloc_strdup(NULL, "This is a test string");;
+       return pytalloc_steal(pytalloc_GetObjectType(), obj);
+}
+
+static PyObject *testpytalloc_get_object_type(PyObject *mod) {
+       PyObject *type = (PyObject *)pytalloc_GetObjectType();
+       Py_INCREF(type);
+       return type;
+}
+
+static PyObject *testpytalloc_reference(PyObject *mod, PyObject *args) {
+       pytalloc_Object *source = NULL;
+       void *ptr;
+
+       if (!PyArg_ParseTuple(args, "O!", pytalloc_GetObjectType(), &source))
+               return NULL;
+
+       ptr = source->ptr;
+       return pytalloc_reference_ex(pytalloc_GetObjectType(), ptr, ptr);
+}
+
+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"},
+       { "reference", (PyCFunction)testpytalloc_reference, METH_VARARGS,
+               "call pytalloc_reference_ex"},
+       { NULL }
+};
+
+static PyTypeObject DObject_Type;
+
+static int dobject_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 *dobject_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, dobject_destructor);
+       return pytalloc_steal(&DObject_Type, obj);
+}
+
+static PyTypeObject DObject_Type = {
+       .tp_name = "_test_pytalloc.DObject",
+       .tp_basicsize = sizeof(pytalloc_Object),
+       .tp_methods = NULL,
+       .tp_new = dobject_new,
+       .tp_flags = Py_TPFLAGS_DEFAULT,
+       .tp_doc = "test talloc object that calls a function when underlying data is freed\n",
+};
+
+#define MODULE_DOC "Test utility module for pytalloc"
+
+void init_test_pytalloc(void);
+void init_test_pytalloc(void)
+{
+       PyObject *m;
+
+       DObject_Type.tp_base = pytalloc_GetObjectType();
+       if (PyType_Ready(&DObject_Type) < 0) {
+               return;
+       }
+
+       m = Py_InitModule3("_test_pytalloc", test_talloc_methods, MODULE_DOC);
+
+       if (m == NULL) {
+               return;
+       }
+
+       Py_INCREF(&DObject_Type);
+       Py_INCREF(DObject_Type.tp_base);
+       PyModule_AddObject(m, "DObject", (PyObject *)&DObject_Type);
+}
diff --git a/lib/talloc/test_pytalloc.py b/lib/talloc/test_pytalloc.py
new file mode 100644 (file)
index 0000000..961bfcb
--- /dev/null
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+# Simple tests for the talloc python bindings.
+# Copyright (C) 2015 Petr Viktorin <pviktori@redhat.com>
+
+import unittest
+import subprocess
+import sys
+import re
+import gc
+
+import talloc
+import _test_pytalloc
+
+def dummy_func():
+    pass
+
+
+class TallocTests(unittest.TestCase):
+
+    def test_report_full(self):
+        # report_full is hardcoded to print to stdout, so use a subprocess
+        process = subprocess.Popen([
+            sys.executable, '-c',
+            """if True:
+            import talloc, _test_pytalloc
+            obj = _test_pytalloc.new()
+            talloc.report_full(obj)
+            """
+        ], stdout=subprocess.PIPE)
+        output, stderr = process.communicate()
+        output = str(output)
+        self.assertTrue("full talloc report on 'talloc.Object" in output)
+        self.assertTrue("This is a test string" in output)
+
+    def test_totalblocks(self):
+        obj = _test_pytalloc.new()
+        # Two blocks: the string, and the name
+        self.assertEqual(talloc.total_blocks(obj), 2)
+
+    def test_repr(self):
+        obj = _test_pytalloc.new()
+        prefix = '<talloc.Object talloc 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 = []
+        obj = _test_pytalloc.DObject(lambda: lst.append('dead'))
+        self.assertEqual(lst, [])
+        del obj
+        gc.collect()
+        self.assertEqual(lst, ['dead'])
+
+
+class TallocComparisonTests(unittest.TestCase):
+
+    def test_compare_same(self):
+        obj1 = _test_pytalloc.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.new(),
+            _test_pytalloc.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 talloc.Object < _test_pytalloc.DObject:
+            obj1 = _test_pytalloc.new()
+            obj2 = _test_pytalloc.DObject(dummy_func)
+        else:
+            obj2 = _test_pytalloc.new()
+            obj1 = _test_pytalloc.DObject(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):
+        # Check correct lifetime of the talloc'd data with multiple references
+        lst = []
+        obj = _test_pytalloc.DObject(lambda: lst.append('dead'))
+        ref = _test_pytalloc.reference(obj)
+        del obj
+        gc.collect()
+        self.assertEqual(lst, [])
+        del ref
+        gc.collect()
+        self.assertEqual(lst, ['dead'])
+
+
+if __name__ == '__main__':
+    unittest.TestProgram()
index a1b72a076137ac933f162b4b5f7f7b66d7707b8e..136798839840c33d84a1201e09c5683c9ed96925 100644 (file)
@@ -136,13 +136,30 @@ def build(bld):
                          enabled=True,
                          realname='talloc.so')
 
+        bld.SAMBA_PYTHON('test_pytalloc',
+                         'test_pytalloc.c',
+                         deps='pytalloc',
+                         enabled=True,
+                         realname='_test_pytalloc.so',
+                         install=False)
+
 def test(ctx):
     '''run talloc testsuite'''
     import Utils, samba_utils
+    env = samba_utils.LOAD_ENVIRONMENT()
     cmd = os.path.join(Utils.g_module.blddir, 'talloc_testsuite')
     ret = samba_utils.RUN_COMMAND(cmd)
     print("testsuite returned %d" % ret)
-    sys.exit(ret)
+    if 'USING_SYSTEM_PYTALLOC_UTIL' not in env.defines and not env.disable_python:
+        cmd = "PYTHONPATH=%s %s test_pytalloc.py" % (
+            os.path.join(Utils.g_module.blddir, 'python'),
+            env['PYTHON'],
+        )
+        pyret = samba_utils.RUN_COMMAND(cmd)
+    else:
+        pyret = 0
+    print("python testsuite returned %d" % pyret)
+    sys.exit(ret or pyret)
 
 def dist():
     '''makes a tarball for distribution'''