blob: 7732d16a66b5fb6801f5d2a7087d487b01e384a5 [file] [log] [blame]
Daniel Veillard36e5cd52004-11-02 14:52:23 +00001#!/usr/bin/python -u
2#
3# generate a tester program for the API
4#
5import sys
6import string
7try:
8 import libxml2
9except:
10 print "libxml2 python bindings not available, skipping testapi.c generation"
11 sys.exit(0)
12
13#
14# Modules we don't want skip in API test
15#
16skipped_modules = [ "SAX", "SAX2", "xlink", "threads", "globals",
17 "xpathInternals", "xmlunicode", "parserInternals", "xmlmemory",
18 "xmlversion", "debugXML" ]
19
20#
21# Some function really need to be skipped for the tests.
22#
23skipped_functions = [ "xmlFdRead", "xmlReadFd", "xmlCtxtReadFd",
24 "xmlCleanupParser" ]
25
26#
27# Those functions have side effect on the global state
28# and hence generate errors on memory allocation tests
29#
30skipped_memcheck = [ "xmlLoadCatalog", "xmlAddEncodingAlias",
31 "xmlSchemaInitTypes", "xmlNanoFTPProxy", "xmlNanoFTPScanProxy",
32 "xmlNanoHTTPScanProxy", "xmlResetLastError", "xmlCatalogConvert",
33 "xmlCatalogRemove", "xmlLoadCatalogs", "xmlCleanupCharEncodingHandlers",
34 "xmlInitCharEncodingHandlers" ]
35
36modules = []
37
38def is_skipped_module(name):
39 for mod in skipped_modules:
40 if mod == name:
41 return 1
42 return 0
43
44def is_skipped_function(name):
45 for fun in skipped_functions:
46 if fun == name:
47 return 1
48 # Do not test destructors
49 if string.find(name, 'Free') != -1:
50 return 1
51 return 0
52
53def is_skipped_memcheck(name):
54 for fun in skipped_memcheck:
55 if fun == name:
56 return 1
57 return 0
58
59missing_types = {}
60def add_missing_type(name, func):
61 try:
62 list = missing_types[name]
63 list.append(func)
64 except:
65 missing_types[name] = [func]
66
67#
68# Open the input API description and the C test program result
69#
70doc = libxml2.readFile('doc/libxml2-api.xml', None, 0)
71if doc == None:
72 print "Failed to load doc/libxml2-api.xml"
73 sys.exit(1)
74test = open('testapi.c', 'w')
75ctxt = doc.xpathNewContext()
76headers = ctxt.xpathEval("/api/files/file")
77
78#
79# Generate the test header
80#
81test.write("""/*
82 * testapi.c: libxml2 API tester program.
83 *
84 * Automatically generated by gentest.py from libxml2-api.xml
85 *
86 * See Copyright for the status of this software.
87 *
88 * daniel@veillard.com
89 */
90
91#include <stdio.h>
92#include <libxml/xmlerror.h>
93
94static int testlibxml2(void);
95
96static int generic_errors = 0;
97static int call_tests = 0;
98
99static void
100structured_errors(void *userData ATTRIBUTE_UNUSED,
101 xmlErrorPtr error ATTRIBUTE_UNUSED) {
102 generic_errors++;
103}
104
105int main(void) {
106 int ret;
107 int blocks, mem;
108
109 LIBXML_TEST_VERSION
110
111 xmlSetStructuredErrorFunc(NULL, structured_errors);
112
113 ret = testlibxml2();
114
115 xmlCleanupParser();
116 blocks = xmlMemBlocks();
117 mem = xmlMemUsed();
118 if ((blocks != 0) || (mem != 0)) {
119 printf("testapi leaked %d bytes in %d blocks\\n", mem, blocks);
120 }
121 xmlMemoryDump();
122
123 return (ret != 0);
124}
125
126""");
127
128#
129# Load the interfaces
130#
131for file in headers:
132 name = file.xpathEval('string(@name)')
133 if (name == None) or (name == ''):
134 continue
135
136 #
137 # do not test deprecated APIs
138 #
139 desc = file.xpathEval('string(description)')
140 if string.find(desc, 'DEPRECATED') != -1:
141 print "Skipping deprecated interface %s" % name
142 continue;
143
144 #
145 # Some module may be skipped because they don't really consists
146 # of user callable APIs
147 #
148 if is_skipped_module(name):
149 continue
150
151 test.write("#include <libxml/%s.h>\n" % name)
152 modules.append(name)
153
154#
155# Generate the callers signatures
156#
157for module in modules:
158 test.write("static int test_%s(void);\n" % module);
159
160#
161# Provide the type generators and destructors for the parameters
162#
163
164def type_convert(str, name, info, module, function):
165 res = string.replace(str, " *", "_ptr")
166 res = string.replace(res, " ", "_")
167 if res == 'const_char_ptr':
168 if string.find(name, "file") != -1 or \
169 string.find(name, "uri") != -1 or \
170 string.find(name, "URI") != -1 or \
171 string.find(info, "filename") != -1 or \
172 string.find(info, "URI") != -1 or \
173 string.find(info, "URL") != -1:
174 if string.find(function, "Save") != -1:
175 return('fileoutput')
176 return('filepath')
177 if res == 'void_ptr':
178 if module == 'nanoftp' and name == 'ctx':
179 return('xmlNanoFTPCtxtPtr')
180 if module == 'nanohttp' and name == 'ctx':
181 return('xmlNanoHTTPCtxtPtr')
182
183 return res
184
185known_param_types = [ "int", "const_char_ptr", "const_xmlChar_ptr",
186 "xmlParserCtxtPtr", "xmlDocPtr", "filepath", "fileoutput" ];
187
188def is_known_param_type(name):
189 for type in known_param_types:
190 if type == name:
191 return 1
192 return 0
193
194test.write("""
195#define gen_nb_int 4
196
197static int gen_int(int no) {
198 if (no == 0) return(0);
199 if (no == 1) return(1);
200 if (no == 2) return(122);
201 return(-1);
202}
203
204static void des_int(int no ATTRIBUTE_UNUSED, int val ATTRIBUTE_UNUSED) {
205}
206
207#define gen_nb_const_char_ptr 4
208
209static const char *gen_const_char_ptr(int no) {
210 if (no == 0) return("foo");
211 if (no == 1) return("<foo/>");
212 if (no == 2) return("test/ent2");
213 return(NULL);
214}
215static void des_const_char_ptr(int no ATTRIBUTE_UNUSED, const char *val ATTRIBUTE_UNUSED) {
216}
217
218#define gen_nb_const_xmlChar_ptr 5
219
220static const xmlChar *gen_const_xmlChar_ptr(int no) {
221 if (no == 0) return((const xmlChar *) "foo");
222 if (no == 1) return((const xmlChar *) "<foo/>");
223 if (no == 2) return((const xmlChar *) "nøne");
224 if (no == 3) return((const xmlChar *) " 2ab ");
225 return(NULL);
226}
227static void des_const_xmlChar_ptr(int no ATTRIBUTE_UNUSED, const xmlChar *val ATTRIBUTE_UNUSED) {
228}
229
230#define gen_nb_filepath 8
231
232static const char *gen_filepath(int no) {
233 if (no == 0) return("missing.xml");
234 if (no == 1) return("<foo/>");
235 if (no == 2) return("test/ent2");
236 if (no == 3) return("test/valid/REC-xml-19980210.xml");
237 if (no == 4) return("test/valid/xhtml1-strict.dtd");
238 if (no == 5) return("http://missing.example.org/");
239 if (no == 6) return("http://missing. example.org/");
240 return(NULL);
241}
242static void des_filepath(int no ATTRIBUTE_UNUSED, const char *val ATTRIBUTE_UNUSED) {
243}
244
245#define gen_nb_fileoutput 6
246
247static const char *gen_fileoutput(int no) {
248 if (no == 0) return("/missing.xml");
249 if (no == 1) return("<foo/>");
250 if (no == 2) return("ftp://missing.example.org/foo");
251 if (no == 3) return("http://missing.example.org/");
252 if (no == 4) return("http://missing. example.org/");
253 return(NULL);
254}
255static void des_fileoutput(int no ATTRIBUTE_UNUSED, const char *val ATTRIBUTE_UNUSED) {
256}
257
258#define gen_nb_xmlParserCtxtPtr 2
259static xmlParserCtxtPtr gen_xmlParserCtxtPtr(int no) {
260 if (no == 0) return(xmlNewParserCtxt());
261 return(NULL);
262}
263static void des_xmlParserCtxtPtr(int no ATTRIBUTE_UNUSED, xmlParserCtxtPtr val) {
264 if (val != NULL)
265 xmlFreeParserCtxt(val);
266}
267
268#define gen_nb_xmlDocPtr 2
269static xmlDocPtr gen_xmlDocPtr(int no) {
270 if (no == 0) return(xmlNewDoc(BAD_CAST "1.0"));
271 return(NULL);
272}
273static void des_xmlDocPtr(int no ATTRIBUTE_UNUSED, xmlDocPtr val) {
274 if (val != NULL)
275 xmlFreeDoc(val);
276}
277
278""");
279
280#
281# Provide the type destructors for the return values
282#
283
284known_return_types = [ "int", "const_char_ptr", "xmlDocPtr", "xmlNodePtr" ];
285
286def is_known_return_type(name):
287 for type in known_return_types:
288 if type == name:
289 return 1
290 return 0
291
292test.write("""
293static void desret_int(int val ATTRIBUTE_UNUSED) {
294}
295static void desret_const_char_ptr(const char *val ATTRIBUTE_UNUSED) {
296}
297static void desret_xmlDocPtr(xmlDocPtr val) {
298 xmlFreeDoc(val);
299}
300static void desret_xmlNodePtr(xmlNodePtr val) {
301 xmlUnlinkNode(val);
302 xmlFreeNode(val);
303}
304""");
305
306#
307# Generate the top caller
308#
309
310test.write("""
311/**
312 * testlibxml2:
313 *
314 * Main entry point of the tester for the full libxml2 module,
315 * it calls all the tester entry point for each module.
316 *
317 * Returns the number of error found
318 */
319static int
320testlibxml2(void)
321{
322 int ret = 0;
323
324""")
325
326for module in modules:
327 test.write(" ret += test_%s();\n" % module)
328
329test.write("""
330 printf("Total: %d tests, %d errors\\n", call_tests, ret);
331 return(ret);
332}
333
334""")
335
336#
337# How to handle a function
338#
339nb_tests = 0
340
341def generate_test(module, node):
342 global test
343 global nb_tests
344 nb_cond = 0
345 no_gen = 0
346
347 name = node.xpathEval('string(@name)')
348 if is_skipped_function(name):
349 return
350
351 test.write("""
352static int
353test_%s(void) {
354 int ret = 0;
355
356""" % (name))
357
358 #
359 # check we know how to handle the args and return values
360 # and store the informations for the generation
361 #
362 try:
363 args = node.xpathEval("arg")
364 except:
365 args = []
366 t_args = []
367 for arg in args:
368 rtype = arg.xpathEval("string(@type)")
369 if rtype == 'void':
370 break;
371 info = arg.xpathEval("string(@info)")
372 nam = arg.xpathEval("string(@name)")
373 type = type_convert(rtype, nam, info, module, name)
374 if is_known_param_type(type) == 0:
375 add_missing_type(type, name);
376 no_gen = 1
377 t_args.append((nam, type, rtype, info))
378
379 try:
380 rets = node.xpathEval("return")
381 except:
382 rets = []
383 t_ret = None
384 for ret in rets:
385 rtype = ret.xpathEval("string(@type)")
386 info = ret.xpathEval("string(@info)")
387 type = type_convert(rtype, 'return', info, module, name)
388 if rtype == 'void':
389 break
390 if is_known_return_type(type) == 0:
391 add_missing_type(type, name);
392 no_gen = 1
393 t_ret = (type, rtype, info)
394 break
395
396 if no_gen == 1:
397 test.write("""
398 /* missing type support */
399 return(ret);
400}
401
402""")
403 return
404
405 try:
406 conds = node.xpathEval("cond")
407 for cond in conds:
408 test.write("#ifdef %s\n" % (cond.get_content()))
409 nb_cond = nb_cond + 1
410 except:
411 pass
412
413 # Declare the memory usage counter
414 no_mem = is_skipped_memcheck(name)
415 if no_mem == 0:
416 test.write(" int mem_base;\n");
417
418 # Declare the return value
419 if t_ret != None:
420 test.write(" %s ret_val;\n" % (t_ret[1]))
421
422 # Declare the arguments
423 for arg in t_args:
424 (nam, type, rtype, info) = arg;
425 # add declaration
426 test.write(" %s %s; /* %s */\n" % (rtype, nam, info))
427 test.write(" int n_%s;\n" % (nam))
428 test.write("\n")
429
430 # Cascade loop on of each argument list of values
431 for arg in t_args:
432 (nam, type, rtype, info) = arg;
433 #
434 test.write(" for (n_%s = 0;n_%s < gen_nb_%s;n_%s++) {\n" % (
435 nam, nam, type, nam))
436
437 # log the memory usage
438 if no_mem == 0:
439 test.write(" mem_base = xmlMemBlocks();\n");
440
441 # prepare the call
442 for arg in t_args:
443 (nam, type, rtype, info) = arg;
444 #
445 test.write(" %s = gen_%s(n_%s);\n" % (nam, type, nam))
446
447 # do the call, and clanup the result
448 if t_ret != None:
449 test.write("\n ret_val = %s(" % (name))
450 need = 0
451 for arg in t_args:
452 (nam, type, rtype, info) = arg
453 if need:
454 test.write(", ")
455 else:
456 need = 1
457 test.write("%s" % nam);
458 test.write(");\n desret_%s(ret_val);\n" % t_ret[0])
459 else:
460 test.write("\n %s(" % (name));
461 need = 0;
462 for arg in t_args:
463 (nam, type, rtype, info) = arg;
464 if need:
465 test.write(", ")
466 else:
467 need = 1
468 test.write("%s" % nam)
469 test.write(");\n")
470 test.write(" call_tests++;\n");
471
472 # Free the arguments
473 for arg in t_args:
474 (nam, type, rtype, info) = arg;
475 #
476 test.write(" des_%s(n_%s, %s);\n" % (type, nam, nam))
477
478 test.write(" xmlResetLastError();\n");
479 # Check the memory usage
480 if no_mem == 0:
481 test.write(""" if (mem_base != xmlMemBlocks()) {
482 printf("Leak of %%d blocks found in %s\\n",
483 xmlMemBlocks() - mem_base);
484 ret++;
485 }
486""" % (name));
487
488 for arg in t_args:
489 test.write(" }\n")
490
491 #
492 # end of conditional
493 #
494 while nb_cond > 0:
495 test.write("#endif\n")
496 nb_cond = nb_cond -1
497
498 nb_tests = nb_tests + 1;
499
500 test.write("""
501 return(ret);
502}
503
504""")
505
506#
507# Generate all module callers
508#
509for module in modules:
510 # gather all the functions exported by that module
511 try:
512 functions = ctxt.xpathEval("/api/symbols/function[@file='%s']" % (module))
513 except:
514 print "Failed to gather functions from module %s" % (module)
515 continue;
516
517 # iterate over all functions in the module generating the test
518 for function in functions:
519 generate_test(module, function);
520
521 # header
522 test.write("""static int
523test_%s(void) {
524 int ret = 0;
525
526 printf("Testing %s ...\\n");
527""" % (module, module))
528
529 # iterate over all functions in the module generating the call
530 for function in functions:
531 name = function.xpathEval('string(@name)')
532 if is_skipped_function(name):
533 continue
534 test.write(" ret += test_%s();\n" % (name))
535
536 # footer
537 test.write("""
538 if (ret != 0)
539 printf("Module %s: %%d errors\\n", ret);
540 return(ret);
541}
542""" % (module))
543
544print "Generated test for %d modules and %d functions" %(len(modules), nb_tests)
545nr = 0
546miss = 'none'
547for missing in missing_types.keys():
548 n = len(missing_types[missing])
549 if n > nr:
550 miss = missing
551 nr = n
552
553if nr > 0:
554 print "most needed type support: %s %d times" % (miss, nr)
555
556