sanitytest: add some special cases for virNetworkPort APIs
[libvirt-python/ericb.git] / sanitytest.py
blob9743f6f3c5501a67c10c71becabeb468650234ed
1 #!/usr/bin/env python
3 import sys
4 import lxml
5 import lxml.etree
6 import string
8 if len(sys.argv) >= 2:
9 # Munge import path to insert build location for libvirt mod
10 sys.path.insert(0, sys.argv[1])
11 import libvirt
13 if sys.version > '3':
14 long = int
16 def get_libvirt_api_xml_path():
17 import subprocess
18 args = ["pkg-config", "--variable", "libvirt_api", "libvirt"]
19 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
20 stdout, _ = proc.communicate()
21 if proc.returncode:
22 sys.exit(proc.returncode)
23 return stdout.splitlines()[0]
25 # Path to the libvirt API XML file
26 if len(sys.argv) >= 3:
27 xml = sys.argv[2]
28 else:
29 xml = get_libvirt_api_xml_path()
31 with open(xml, "r") as fp:
32 tree = lxml.etree.parse(fp)
34 verbose = False
35 fail = False
37 enumvals = {}
38 second_pass = []
39 wantenums = []
40 wantfunctions = []
42 # Phase 1: Identify all functions and enums in public API
43 set = tree.xpath('/api/files/file/exports[@type="function"]/@symbol')
44 for n in set:
45 wantfunctions.append(n)
47 set = tree.xpath('/api/symbols/enum')
48 for n in set:
49 typ = n.attrib['type']
50 name = n.attrib['name']
51 val = n.attrib['value']
53 if typ not in enumvals:
54 enumvals[typ] = {}
56 # If the value cannot be converted to int, it is reference to
57 # another enum and needs to be sorted out later on
58 try:
59 val = int(val)
60 except ValueError:
61 second_pass.append(n)
62 continue
64 enumvals[typ][name] = int(val)
66 for n in second_pass:
67 typ = n.attrib['type']
68 name = n.attrib['name']
69 val = n.attrib['value']
71 for v in enumvals.values():
72 if val in v:
73 val = int(v[val])
74 break
76 if type(val) != int:
77 fail = True
78 print("Cannot get a value of enum %s (originally %s)" % (val, name))
79 enumvals[typ][name] = val
81 set = tree.xpath('/api/files/file/exports[@type="enum"]/@symbol')
82 for n in set:
83 for enumval in enumvals.values():
84 if n in enumval:
85 enum = enumval
86 break
87 # Eliminate sentinels
88 if n.endswith('_LAST') and enum[n] == max(enum.values()):
89 continue
90 wantenums.append(n)
92 # Phase 2: Identify all classes and methods in the 'libvirt' python module
93 gotenums = []
94 gottypes = []
95 gotfunctions = { "libvirt": [] }
97 for name in dir(libvirt):
98 if name[0] == '_':
99 continue
100 thing = getattr(libvirt, name)
101 # Special-case libvirtError to deal with python 2.4 difference
102 # in Exception class type reporting.
103 if type(thing) in (int, long):
104 gotenums.append(name)
105 elif type(thing) == type or name == "libvirtError":
106 gottypes.append(name)
107 gotfunctions[name] = []
108 elif callable(thing):
109 gotfunctions["libvirt"].append(name)
110 else:
111 pass
113 for enum in wantenums:
114 if enum not in gotenums:
115 fail = True
116 for typ, enumval in enumvals.items():
117 if enum in enumval:
118 print("FAIL Missing exported enum %s of type %s" % (enum, typ))
120 for klassname in gottypes:
121 klassobj = getattr(libvirt, klassname)
122 for name in dir(klassobj):
123 if name[0] == '_':
124 continue
125 if name == 'c_pointer':
126 continue
127 thing = getattr(klassobj, name)
128 if callable(thing):
129 gotfunctions[klassname].append(name)
130 else:
131 pass
134 # Phase 3: First cut at mapping of C APIs to python classes + methods
135 basicklassmap = {}
137 for cname in wantfunctions:
138 name = cname
139 # Some virConnect APIs have stupid names
140 if name[0:7] == "virNode" and name[0:13] != "virNodeDevice":
141 name = "virConnect" + name[7:]
142 if name[0:7] == "virConn" and name[0:10] != "virConnect":
143 name = "virConnect" + name[7:]
145 # The typed param APIs are only for internal use
146 if name[0:14] == "virTypedParams":
147 continue
149 if name[0:23] == "virNetworkDHCPLeaseFree":
150 continue
152 if name[0:28] == "virDomainStatsRecordListFree":
153 continue
155 if name[0:19] == "virDomainFSInfoFree":
156 continue
158 if name[0:25] == "virDomainIOThreadInfoFree":
159 continue
161 if name[0:22] == "virDomainInterfaceFree":
162 continue
164 if name[0:21] == "virDomainListGetStats":
165 name = "virConnectDomainListGetStats"
167 # These aren't functions, they're callback signatures
168 if name in ["virConnectAuthCallbackPtr", "virConnectCloseFunc",
169 "virStreamSinkFunc", "virStreamSourceFunc", "virStreamEventCallback",
170 "virEventHandleCallback", "virEventTimeoutCallback", "virFreeCallback",
171 "virStreamSinkHoleFunc", "virStreamSourceHoleFunc", "virStreamSourceSkipFunc"]:
172 continue
173 if name[0:21] == "virConnectDomainEvent" and name[-8:] == "Callback":
174 continue
175 if name[0:22] == "virConnectNetworkEvent" and name[-8:] == "Callback":
176 continue
177 if (name.startswith("virConnectStoragePoolEvent") and
178 name.endswith("Callback")):
179 continue
180 if (name.startswith("virConnectNodeDeviceEvent") and
181 name.endswith("Callback")):
182 continue
183 if (name.startswith("virConnectSecretEvent") and
184 name.endswith("Callback")):
185 continue
188 # virEvent APIs go into main 'libvirt' namespace not any class
189 if name[0:8] == "virEvent":
190 if name[-4:] == "Func":
191 continue
192 basicklassmap[name] = ["libvirt", name, cname]
193 else:
194 found = False
195 # To start with map APIs to classes based on the
196 # naming prefix. Mistakes will be fixed in next
197 # loop
198 for klassname in gottypes:
199 klen = len(klassname)
200 if name[0:klen] == klassname:
201 found = True
202 if name not in basicklassmap:
203 basicklassmap[name] = [klassname, name[klen:], cname]
204 elif len(basicklassmap[name]) < klen:
205 basicklassmap[name] = [klassname, name[klen:], cname]
207 # Anything which can't map to a class goes into the
208 # global namespaces
209 if not found:
210 basicklassmap[name] = ["libvirt", name[3:], cname]
213 # Phase 4: Deal with oh so many special cases in C -> python mapping
214 finalklassmap = {}
216 for name in sorted(basicklassmap):
217 klass = basicklassmap[name][0]
218 func = basicklassmap[name][1]
219 cname = basicklassmap[name][2]
221 # The object lifecycle APIs are irrelevant since they're
222 # used inside the object constructors/destructors.
223 if func in ["Ref", "Free", "New", "GetConnect", "GetDomain", "GetNetwork"]:
224 if klass == "virStream" and func == "New":
225 klass = "virConnect"
226 func = "NewStream"
227 else:
228 continue
231 # All the error handling methods need special handling
232 if klass == "libvirt":
233 if func in ["CopyLastError", "DefaultErrorFunc",
234 "ErrorFunc", "FreeError",
235 "SaveLastError", "ResetError"]:
236 continue
237 elif func in ["GetLastError", "GetLastErrorMessage",
238 "GetLastErrorCode", "GetLastErrorDomain",
239 "ResetLastError", "Initialize"]:
240 func = "vir" + func
241 elif func == "SetErrorFunc":
242 func = "RegisterErrorHandler"
243 elif klass == "virConnect":
244 if func in ["CopyLastError", "SetErrorFunc"]:
245 continue
246 elif func in ["GetLastError", "ResetLastError"]:
247 func = "virConn" + func
249 # Remove 'Get' prefix from most APIs, except those in virConnect
250 # and virDomainSnapshot namespaces which stupidly used a different
251 # convention which we now can't fix without breaking API
252 if func[0:3] == "Get" and klass not in ["virConnect", "virDomainSnapshot", "libvirt"]:
253 if func not in ["GetCPUStats", "GetTime"]:
254 func = func[3:]
256 # The object creation and lookup APIs all have to get re-mapped
257 # into the parent class
258 if func in ["CreateXML", "CreateLinux", "CreateXMLWithFiles",
259 "DefineXML", "CreateXMLFrom", "LookupByUUID",
260 "LookupByUUIDString", "LookupByVolume" "LookupByName",
261 "LookupByID", "LookupByName", "LookupByKey", "LookupByPath",
262 "LookupByMACString", "LookupByUsage", "LookupByVolume",
263 "LookupByTargetPath","LookupSCSIHostByWWN", "LookupByPortDev",
264 "Restore", "RestoreFlags",
265 "SaveImageDefineXML", "SaveImageGetXMLDesc", "DefineXMLFlags"]:
266 if klass != "virDomain":
267 func = klass[3:] + func
269 if klass == "virDomainSnapshot":
270 klass = "virDomain"
271 func = func[6:]
272 elif klass == "virStorageVol" and func in ["StorageVolCreateXMLFrom", "StorageVolCreateXML"]:
273 klass = "virStoragePool"
274 func = func[10:]
275 elif klass == "virNetworkPort":
276 klass = "virNetwork"
277 func = func[7:]
278 elif func == "StoragePoolLookupByVolume":
279 klass = "virStorageVol"
280 elif func == "StorageVolLookupByName":
281 klass = "virStoragePool"
282 else:
283 klass = "virConnect"
285 # The open methods get remapped to primary namespace
286 if klass == "virConnect" and func in ["Open", "OpenAuth", "OpenReadOnly"]:
287 klass = "libvirt"
289 # These are inexplicably renamed in the python API
290 if func == "ListDomains":
291 func = "ListDomainsID"
292 elif func == "ListAllNodeDevices":
293 func = "ListAllDevices"
294 elif func == "ListNodeDevices":
295 func = "ListDevices"
297 # The virInterfaceChangeXXXX APIs go into virConnect. Stupidly
298 # they have lost their 'interface' prefix in names, but we can't
299 # fix this name
300 if func[0:6] == "Change":
301 klass = "virConnect"
303 # Need to special case the snapshot APIs
304 if klass == "virDomainSnapshot" and func in ["Current", "ListNames", "Num"]:
305 klass = "virDomain"
306 func = "snapshot" + func
308 # Names should start with lowercase letter...
309 func = func[0:1].lower() + func[1:]
310 if func[0:8] == "nWFilter":
311 func = "nwfilter" + func[8:]
312 if func[0:8] == "fSFreeze" or func[0:6] == "fSThaw" or func[0:6] == "fSInfo":
313 func = "fs" + func[2:]
314 if func[0:12] == "iOThreadInfo":
315 func = "ioThreadInfo"
317 if klass == "virNetwork":
318 func = func.replace("dHCP", "DHCP")
320 # ...except when they don't. More stupid naming
321 # decisions we can't fix
322 if func == "iD":
323 func = "ID"
324 if func == "uUID":
325 func = "UUID"
326 if func == "uUIDString":
327 func = "UUIDString"
328 if func == "oSType":
329 func = "OSType"
330 if func == "xMLDesc":
331 func = "XMLDesc"
332 if func == "mACString":
333 func = "MACString"
335 finalklassmap[name] = [klass, func, cname]
338 # Phase 5: Validate sure that every C API is mapped to a python API
339 usedfunctions = {}
340 for name in sorted(finalklassmap):
341 klass = finalklassmap[name][0]
342 func = finalklassmap[name][1]
344 if func in gotfunctions[klass]:
345 usedfunctions["%s.%s" % (klass, func)] = 1
346 if verbose:
347 print("PASS %s -> %s.%s" % (name, klass, func))
348 else:
349 print("FAIL %s -> %s.%s (C API not mapped to python)" % (name, klass, func))
350 fail = True
353 # Phase 6: Validate that every python API has a corresponding C API
354 for klass in gotfunctions:
355 if klass == "libvirtError":
356 continue
357 for func in sorted(gotfunctions[klass]):
358 # These are pure python methods with no C APi
359 if func in ["connect", "getConnect", "domain", "getDomain",
360 "virEventInvokeFreeCallback",
361 "sparseRecvAll", "sparseSendAll"]:
362 continue
364 key = "%s.%s" % (klass, func)
365 if not key in usedfunctions:
366 print("FAIL %s.%s (Python API not mapped to C)" % (klass, func))
367 fail = True
368 else:
369 if verbose:
370 print("PASS %s.%s" % (klass, func))
372 # Phase 7: Validate that all the low level C APIs have binding
373 for name in sorted(finalklassmap):
374 cname = finalklassmap[name][2]
376 pyname = cname
377 if pyname == "virSetErrorFunc":
378 pyname = "virRegisterErrorHandler"
379 elif pyname == "virConnectListDomains":
380 pyname = "virConnectListDomainsID"
382 # These exist in C and exist in python, but we've got
383 # a pure-python impl so don't check them
384 if name in ["virStreamRecvAll", "virStreamSendAll",
385 "virStreamSparseRecvAll", "virStreamSparseSendAll"]:
386 continue
388 try:
389 thing = getattr(libvirt.libvirtmod, pyname)
390 except AttributeError:
391 print("FAIL libvirt.libvirtmod.%s (C binding does not exist)" % pyname)
392 fail = True
394 if fail:
395 sys.exit(1)
396 else:
397 sys.exit(0)