On Tue, Nov 26, 2013 at 12:32 PM, Daniel P. Berrange <berrange@xxxxxxxxxx> wrote: > From: "Daniel P. Berrange" <berrange@xxxxxxxxxx> > > Validate that every public API method is mapped into the python > and that every python method has a sane C API. Looks like we had the same idea and even a similar approach as well. > > Signed-off-by: Daniel P. Berrange <berrange@xxxxxxxxxx> > --- > 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 I wouldn't directly import libvirtmod due to Cygwin. I'd just use libvirt.libvirtmod which is what its available as. > + > +# 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) > + Maybe its a bit ugly but I actually grabbed the typedef's as well to check the various namespaces (e.g. virConnect, virDomain) but not sure if we want that. > + > +# 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 Could the body of this be made into a function reused below? > + > +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 > > -- > libvir-list mailing list > libvir-list@xxxxxxxxxx > https://www.redhat.com/mailman/listinfo/libvir-list Just some visual comments until I get a chance to really play with this. I stopped at the fixup area, which in my code is equally painful as well. You're obviously a bit more knowledgable about Python than I am because your fixups are a bit cleaner. -- Doug Goldstein -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list