[libvirt] [PATCH python] Improve quality of sanitytest check

Daniel P. Berrange berrange at redhat.com
Tue Nov 26 18:32:38 UTC 2013


From: "Daniel P. Berrange" <berrange at redhat.com>

Validate that every public API method is mapped into the python
and that every python method has a sane C API.

Signed-off-by: Daniel P. Berrange <berrange at redhat.com>
---
 sanitytest.py | 309 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
 setup.py      |  35 +++----
 2 files changed, 294 insertions(+), 50 deletions(-)
 mode change 100755 => 100644 sanitytest.py

diff --git a/sanitytest.py b/sanitytest.py
old mode 100755
new mode 100644
index 517054b..9e4c261
--- a/sanitytest.py
+++ b/sanitytest.py
@@ -1,40 +1,283 @@
 #!/usr/bin/python
 
 import sys
+import lxml
+import lxml.etree
+import string
 
+# Munge import path to insert build location for libvirt mod
 sys.path.insert(0, sys.argv[1])
-
 import libvirt
+import libvirtmod
+
+# Path to the libvirt API XML file
+xml = sys.argv[2]
+
+f = open(xml, "r")
+tree = lxml.etree.parse(f)
+
+verbose = False
+
+wantenums = []
+wantfunctions = []
+
+# Phase 1: Identify all functions and enums in public API
+set = tree.xpath('/api/files/file/exports[@type="function"]/@symbol')
+for n in set:
+    wantfunctions.append(n)
+
+set = tree.xpath('/api/files/file/exports[@type="enum"]/@symbol')
+for n in set:
+    wantenums.append(n)
+
+
+# Phase 2: Identify all classes and methods in the 'libvirt' python module
+gotenums = []
+gottypes = []
+gotfunctions = { "libvirt": [] }
+
+for name in dir(libvirt):
+    if name[0] == '_':
+        continue
+    thing = getattr(libvirt, name)
+    if type(thing) == int:
+        gotenums.append(name)
+    elif type(thing) == type:
+        gottypes.append(name)
+        gotfunctions[name] = []
+    elif callable(thing):
+        gotfunctions["libvirt"].append(name)
+    else:
+       pass
+
+for klassname in gottypes:
+    klassobj = getattr(libvirt, klassname)
+    for name in dir(klassobj):
+        if name[0] == '_':
+            continue
+        thing = getattr(klassobj, name)
+        if callable(thing):
+            gotfunctions[klassname].append(name)
+        else:
+            pass
+
+
+# Phase 3: First cut at mapping of C APIs to python classes + methods
+basicklassmap = {}
+
+for cname in wantfunctions:
+    name = cname
+    # Some virConnect APIs have stupid names
+    if name[0:7] == "virNode" and name[0:13] != "virNodeDevice":
+        name = "virConnect" + name[7:]
+    if name[0:7] == "virConn" and name[0:10] != "virConnect":
+        name = "virConnect" + name[7:]
+
+    # The typed param APIs are only for internal use
+    if name[0:14] == "virTypedParams":
+        continue
+
+    # These aren't functions, they're callback signatures
+    if name in ["virConnectAuthCallbackPtr", "virConnectCloseFunc",
+                "virStreamSinkFunc", "virStreamSourceFunc", "virStreamEventCallback",
+                "virEventHandleCallback", "virEventTimeoutCallback", "virFreeCallback"]:
+        continue
+    if name[0:21] == "virConnectDomainEvent" and name[-8:] == "Callback":
+        continue
+
+
+    # virEvent APIs go into main 'libvirt' namespace not any class
+    if name[0:8] == "virEvent":
+        if name[-4:] == "Func":
+            continue
+        basicklassmap[name] = ["libvirt", name, cname]
+    else:
+        found = False
+        # To start with map APIs to classes based on the
+        # naming prefix. Mistakes will be fixed in next
+        # loop
+        for klassname in gottypes:
+            klen = len(klassname)
+            if name[0:klen] == klassname:
+                found = True
+                if name not in basicklassmap:
+                    basicklassmap[name] = [klassname, name[klen:], cname]
+                elif len(basicklassmap[name]) < klassname:
+                    basicklassmap[name] = [klassname, name[klen:], cname]
+
+        # Anything which can't map to a class goes into the
+        # global namespaces
+        if not found:
+            basicklassmap[name] = ["libvirt", name[3:], cname]
+
+
+# Phase 4: Deal with oh so many special cases in C -> python mapping
+finalklassmap = {}
+
+for name in sorted(basicklassmap):
+    klass = basicklassmap[name][0]
+    func = basicklassmap[name][1]
+    cname = basicklassmap[name][2]
+
+    # The object lifecycle APIs are irrelevant since they're
+    # used inside the object constructors/destructors.
+    if func in ["Ref", "Free", "New", "GetConnect", "GetDomain"]:
+        if klass == "virStream" and func == "New":
+            klass = "virConnect"
+            func = "NewStream"
+        else:
+            continue
+
+
+    # All the error handling methods need special handling
+    if klass == "libvirt":
+        if func in ["CopyLastError", "DefaultErrorFunc",
+                    "ErrorFunc", "FreeError",
+                    "SaveLastError", "ResetError"]:
+            continue
+        elif func in ["GetLastError", "GetLastErrorMessage", "ResetLastError", "Initialize"]:
+            func = "vir" + func
+        elif func == "SetErrorFunc":
+            func = "RegisterErrorHandler"
+    elif klass == "virConnect":
+        if func in ["CopyLastError", "SetErrorFunc"]:
+            continue
+        elif func in ["GetLastError", "ResetLastError"]:
+            func = "virConn" + func
+
+    # Remove 'Get' prefix from most APIs, except those in virConnect
+    # and virDomainSnapshot namespaces which stupidly used a different
+    # convention which we now can't fix without breaking API
+    if func[0:3] == "Get" and klass not in ["virConnect", "virDomainSnapshot", "libvirt"]:
+        if func not in ["GetCPUStats"]:
+            func = func[3:]
+
+    # The object creation and lookup APIs all have to get re-mapped
+    # into the parent class
+    if func in ["CreateXML", "CreateLinux", "CreateXMLWithFiles",
+                "DefineXML", "CreateXMLFrom", "LookupByUUID",
+                "LookupByUUIDString", "LookupByVolume" "LookupByName",
+                "LookupByID", "LookupByName", "LookupByKey", "LookupByPath",
+                "LookupByMACString", "LookupByUsage", "LookupByVolume",
+                "LookupSCSIHostByWWN", "Restore", "RestoreFlags",
+                "SaveImageDefineXML", "SaveImageGetXMLDesc"]:
+        if klass != "virDomain":
+            func = klass[3:] + func
+        
+        if klass == "virDomainSnapshot":
+            klass = "virDomain"
+            func = func[6:]
+        elif klass == "virStorageVol" and func in ["StorageVolCreateXMLFrom", "StorageVolCreateXML"]:
+            klass = "virStoragePool"
+            func = func[10:]
+        elif func == "StoragePoolLookupByVolume":
+            klass = "virStorageVol"
+        elif func == "StorageVolLookupByName":
+            klass = "virStoragePool"
+        else:
+            klass = "virConnect"
+
+    # The open methods get remapped to primary namespace
+    if klass == "virConnect" and func in ["Open", "OpenAuth", "OpenReadOnly"]:
+        klass = "libvirt"
+
+    # These are inexplicably renamed in the python API
+    if func == "ListDomains":
+        func = "ListDomainsID"
+    elif func == "ListAllNodeDevices":
+        func = "ListAllDevices"
+    elif func == "ListNodeDevices":
+        func = "ListDevices"
+
+    # The virInterfaceChangeXXXX APIs go into virConnect. Stupidly
+    # they have lost their 'interface' prefix in names, but we can't
+    # fix this name
+    if func[0:6] == "Change":
+        klass = "virConnect"
+
+    # Need to special case the snapshot APIs
+    if klass == "virDomainSnapshot" and func in ["Current", "ListNames", "Num"]:
+        klass = "virDomain"
+        func = "snapshot" + func
+
+    # Names should stsart with lowercase letter...
+    func = string.lower(func[0:1]) + func[1:]
+    if func[0:8] == "nWFilter":
+        func = "nwfilter" + func[8:]
+
+    # ...except when they don't. More stupid naming
+    # decisions we can't fix
+    if func == "iD":
+        func = "ID"
+    if func == "uUID":
+        func = "UUID"
+    if func == "uUIDString":
+        func = "UUIDString"
+    if func == "oSType":
+        func = "OSType"
+    if func == "xMLDesc":
+        func = "XMLDesc"
+    if func == "mACString":
+        func = "MACString"
+
+    finalklassmap[name] = [klass, func, cname]
+
+
+# Phase 5: Validate sure that every C API is mapped to a python API
+fail = False
+usedfunctions = {}
+for name in sorted(finalklassmap):
+    klass = finalklassmap[name][0]
+    func = finalklassmap[name][1]
+
+    if func in gotfunctions[klass]:
+        usedfunctions["%s.%s" % (klass, func)] = 1
+        if verbose:
+            print "PASS %s -> %s.%s" % (name, klass, func)
+    else:
+        print "FAIL %s -> %s.%s       (C API not mapped to python)" % (name, klass, func)
+        fail = True
+
+
+# Phase 6: Validate that every python API has a corresponding C API
+for klass in gotfunctions:
+    if klass == "libvirtError":
+        continue
+    for func in sorted(gotfunctions[klass]):
+        # These are pure python methods with no C APi
+        if func in ["connect", "getConnect", "domain", "getDomain"]:
+            continue
+
+        key = "%s.%s" % (klass, func)
+        if not key in usedfunctions:
+            print "FAIL %s.%s       (Python API not mapped to C)" % (klass, func)
+            fail = True
+        else:
+            if verbose:
+                print "PASS %s.%s" % (klass, func)
+
+# Phase 7: Validate that all the low level C APIs have binding
+for name in sorted(finalklassmap):
+    cname = finalklassmap[name][2]
+
+    pyname = cname
+    if pyname == "virSetErrorFunc":
+        pyname = "virRegisterErrorHandler"
+    elif pyname == "virConnectListDomains":
+        pyname = "virConnectListDomainsID"
+
+    # These exist in C and exist in python, but we've got
+    # a pure-python impl so don't check them
+    if name in ["virStreamRecvAll", "virStreamSendAll"]:
+        continue
+
+    try:
+        thing = getattr(libvirtmod, pyname)
+    except AttributeError:
+        print "FAIL libvirtmod.%s      (C binding does not exist)" % pyname
+        fail = True
 
-globals = dir(libvirt)
-
-# Sanity test that the generator hasn't gone wrong
-
-# Look for core classes
-for clsname in ["virConnect",
-                "virDomain",
-                "virDomainSnapshot",
-                "virInterface",
-                "virNWFilter",
-                "virNodeDevice",
-                "virNetwork",
-                "virSecret",
-                "virStoragePool",
-                "virStorageVol",
-                "virStream",
-                ]:
-    assert(clsname in globals)
-    assert(object in getattr(libvirt, clsname).__bases__)
-
-# Constants
-assert("VIR_CONNECT_RO" in globals)
-
-# Error related bits
-assert("libvirtError" in globals)
-assert("VIR_ERR_AUTH_FAILED" in globals)
-assert("virGetLastError" in globals)
-
-# Some misc methods
-assert("virInitialize" in globals)
-assert("virEventAddHandle" in globals)
-assert("virEventRegisterDefaultImpl" in globals)
+if fail:
+    sys.exit(1)
+else:
+    sys.exit(0)
diff --git a/setup.py b/setup.py
index 17b4722..bf222f8 100755
--- a/setup.py
+++ b/setup.py
@@ -59,6 +59,20 @@ def get_pkgconfig_data(args, mod, required=True):
 
     return line
 
+def get_api_xml_files():
+    """Check with pkg-config that libvirt is present and extract
+    the API XML file paths we need from it"""
+
+    libvirt_api = get_pkgconfig_data(["--variable", "libvirt_api"], "libvirt")
+
+    offset = libvirt_api.index("-api.xml")
+    libvirt_qemu_api = libvirt_api[0:offset] + "-qemu-api.xml"
+
+    offset = libvirt_api.index("-api.xml")
+    libvirt_lxc_api = libvirt_api[0:offset] + "-lxc-api.xml"
+
+    return (libvirt_api, libvirt_qemu_api, libvirt_lxc_api)
+
 ldflags = get_pkgconfig_data(["--libs-only-L"], "libvirt", False)
 cflags = get_pkgconfig_data(["--cflags"], "libvirt", False)
 
@@ -105,23 +119,8 @@ if have_libvirt_lxc:
 
 class my_build(build):
 
-    def get_api_xml_files(self):
-        """Check with pkg-config that libvirt is present and extract
-        the API XML file paths we need from it"""
-
-        libvirt_api = get_pkgconfig_data(["--variable", "libvirt_api"], "libvirt")
-
-        offset = libvirt_api.index("-api.xml")
-        libvirt_qemu_api = libvirt_api[0:offset] + "-qemu-api.xml"
-
-        offset = libvirt_api.index("-api.xml")
-        libvirt_lxc_api = libvirt_api[0:offset] + "-lxc-api.xml"
-
-        return (libvirt_api, libvirt_qemu_api, libvirt_lxc_api)
-
-
     def run(self):
-        apis = self.get_api_xml_files()
+        apis = get_api_xml_files()
 
         self.spawn(["python", "generator.py", "libvirt", apis[0]])
         self.spawn(["python", "generator.py", "libvirt-qemu", apis[1]])
@@ -266,7 +265,9 @@ class my_test(Command):
         Run test suite
         """
 
-        self.spawn(["python", "sanitytest.py", self.build_platlib])
+        apis = get_api_xml_files()
+
+        self.spawn(["python", "sanitytest.py", self.build_platlib, apis[0]])
 
 
 class my_clean(clean):
-- 
1.8.3.1




More information about the libvir-list mailing list