blob: c74942cf0b2c224c33b6780630fcdcece641ca69 [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",
Daniel Veillarda03e3652004-11-02 18:45:30 +000024 "htmlFdRead", "htmlReadFd", "htmlCtxtReadFd",
Daniel Veillard36e5cd52004-11-02 14:52:23 +000025 "xmlCleanupParser" ]
26
27#
28# Those functions have side effect on the global state
29# and hence generate errors on memory allocation tests
30#
31skipped_memcheck = [ "xmlLoadCatalog", "xmlAddEncodingAlias",
32 "xmlSchemaInitTypes", "xmlNanoFTPProxy", "xmlNanoFTPScanProxy",
33 "xmlNanoHTTPScanProxy", "xmlResetLastError", "xmlCatalogConvert",
34 "xmlCatalogRemove", "xmlLoadCatalogs", "xmlCleanupCharEncodingHandlers",
Daniel Veillarda03e3652004-11-02 18:45:30 +000035 "xmlInitCharEncodingHandlers", "xmlCatalogCleanup",
36 "htmlParseFile" # loads the catalogs
37]
38
39#
40# Extra code needed for some test cases
41#
42extra_post_call = {
43 "xmlAddChild":
44 "if (ret_val == NULL) { xmlFreeNode(cur) ; cur = NULL ; }",
45 "xmlAddChildList":
46 "if (ret_val == NULL) { xmlFreeNodeList(cur) ; cur = NULL ; }",
47 "xmlAddSibling":
48 "if (ret_val == NULL) { xmlFreeNode(elem) ; elem = NULL ; }",
49 "xmlAddNextSibling":
50 "if (ret_val == NULL) { xmlFreeNode(elem) ; elem = NULL ; }",
51 "xmlAddPrevSibling":
52 "if (ret_val == NULL) { xmlFreeNode(elem) ; elem = NULL ; }",
53 "xmlDocSetRootElement":
54 "if (doc == NULL) { xmlFreeNode(root) ; root = NULL ; }",
55 "xmlReplaceNode":
56 """if ((old == NULL) || (old->parent == NULL)) {
57 xmlFreeNode(cur) ; cur = NULL ; }""",
58 "xmlTextMerge":
59 """if ((first != NULL) && (first->type != XML_TEXT_NODE)) {
60 xmlFreeNode(second) ; second = NULL ; }""",
61}
Daniel Veillard36e5cd52004-11-02 14:52:23 +000062
63modules = []
64
65def is_skipped_module(name):
66 for mod in skipped_modules:
67 if mod == name:
68 return 1
69 return 0
70
71def is_skipped_function(name):
72 for fun in skipped_functions:
73 if fun == name:
74 return 1
75 # Do not test destructors
76 if string.find(name, 'Free') != -1:
77 return 1
78 return 0
79
80def is_skipped_memcheck(name):
81 for fun in skipped_memcheck:
82 if fun == name:
83 return 1
84 return 0
85
86missing_types = {}
87def add_missing_type(name, func):
88 try:
89 list = missing_types[name]
90 list.append(func)
91 except:
92 missing_types[name] = [func]
93
94#
95# Open the input API description and the C test program result
96#
97doc = libxml2.readFile('doc/libxml2-api.xml', None, 0)
98if doc == None:
99 print "Failed to load doc/libxml2-api.xml"
100 sys.exit(1)
101test = open('testapi.c', 'w')
102ctxt = doc.xpathNewContext()
103headers = ctxt.xpathEval("/api/files/file")
104
105#
106# Generate the test header
107#
108test.write("""/*
109 * testapi.c: libxml2 API tester program.
110 *
111 * Automatically generated by gentest.py from libxml2-api.xml
112 *
113 * See Copyright for the status of this software.
114 *
115 * daniel@veillard.com
116 */
117
118#include <stdio.h>
119#include <libxml/xmlerror.h>
120
121static int testlibxml2(void);
122
123static int generic_errors = 0;
124static int call_tests = 0;
125
126static void
127structured_errors(void *userData ATTRIBUTE_UNUSED,
128 xmlErrorPtr error ATTRIBUTE_UNUSED) {
129 generic_errors++;
130}
131
132int main(void) {
133 int ret;
134 int blocks, mem;
135
Daniel Veillarda03e3652004-11-02 18:45:30 +0000136 xmlInitParser();
137
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000138 LIBXML_TEST_VERSION
139
140 xmlSetStructuredErrorFunc(NULL, structured_errors);
141
142 ret = testlibxml2();
143
144 xmlCleanupParser();
145 blocks = xmlMemBlocks();
146 mem = xmlMemUsed();
147 if ((blocks != 0) || (mem != 0)) {
148 printf("testapi leaked %d bytes in %d blocks\\n", mem, blocks);
149 }
150 xmlMemoryDump();
151
152 return (ret != 0);
153}
154
155""");
156
157#
158# Load the interfaces
159#
160for file in headers:
161 name = file.xpathEval('string(@name)')
162 if (name == None) or (name == ''):
163 continue
164
165 #
166 # do not test deprecated APIs
167 #
168 desc = file.xpathEval('string(description)')
169 if string.find(desc, 'DEPRECATED') != -1:
170 print "Skipping deprecated interface %s" % name
171 continue;
172
173 #
174 # Some module may be skipped because they don't really consists
175 # of user callable APIs
176 #
177 if is_skipped_module(name):
178 continue
179
180 test.write("#include <libxml/%s.h>\n" % name)
181 modules.append(name)
182
183#
184# Generate the callers signatures
185#
186for module in modules:
187 test.write("static int test_%s(void);\n" % module);
188
189#
190# Provide the type generators and destructors for the parameters
191#
192
Daniel Veillarda03e3652004-11-02 18:45:30 +0000193def type_convert(str, name, info, module, function, pos):
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000194 res = string.replace(str, " *", "_ptr")
195 res = string.replace(res, " ", "_")
Daniel Veillarda03e3652004-11-02 18:45:30 +0000196 res = string.replace(res, "htmlNode", "xmlNode")
197 res = string.replace(res, "htmlDoc", "xmlDoc")
198 res = string.replace(res, "htmlParser", "xmlParser")
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000199 if res == 'const_char_ptr':
200 if string.find(name, "file") != -1 or \
201 string.find(name, "uri") != -1 or \
202 string.find(name, "URI") != -1 or \
203 string.find(info, "filename") != -1 or \
204 string.find(info, "URI") != -1 or \
205 string.find(info, "URL") != -1:
206 if string.find(function, "Save") != -1:
207 return('fileoutput')
208 return('filepath')
209 if res == 'void_ptr':
210 if module == 'nanoftp' and name == 'ctx':
211 return('xmlNanoFTPCtxtPtr')
212 if module == 'nanohttp' and name == 'ctx':
213 return('xmlNanoHTTPCtxtPtr')
Daniel Veillarda03e3652004-11-02 18:45:30 +0000214 if res == 'xmlNodePtr' and pos != 0:
215 if (function == 'xmlAddChild' and pos == 2) or \
216 (function == 'xmlAddChildList' and pos == 2) or \
217 (function == 'xmlAddNextSibling' and pos == 2) or \
218 (function == 'xmlAddSibling' and pos == 2) or \
219 (function == 'xmlDocSetRootElement' and pos == 2) or \
220 (function == 'xmlReplaceNode' and pos == 2) or \
221 (function == 'xmlTextMerge') or \
222 (function == 'xmlAddPrevSibling' and pos == 2):
223 return('xmlNodePtr_in');
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000224
225 return res
226
227known_param_types = [ "int", "const_char_ptr", "const_xmlChar_ptr",
Daniel Veillarda03e3652004-11-02 18:45:30 +0000228 "xmlParserCtxtPtr", "xmlDocPtr", "filepath", "fileoutput" ,
229 "xmlNodePtr", "xmlNodePtr_in" ];
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000230
231def is_known_param_type(name):
232 for type in known_param_types:
233 if type == name:
234 return 1
235 return 0
236
237test.write("""
238#define gen_nb_int 4
239
240static int gen_int(int no) {
241 if (no == 0) return(0);
242 if (no == 1) return(1);
243 if (no == 2) return(122);
244 return(-1);
245}
246
247static void des_int(int no ATTRIBUTE_UNUSED, int val ATTRIBUTE_UNUSED) {
248}
249
250#define gen_nb_const_char_ptr 4
251
252static const char *gen_const_char_ptr(int no) {
253 if (no == 0) return("foo");
254 if (no == 1) return("<foo/>");
255 if (no == 2) return("test/ent2");
256 return(NULL);
257}
258static void des_const_char_ptr(int no ATTRIBUTE_UNUSED, const char *val ATTRIBUTE_UNUSED) {
259}
260
261#define gen_nb_const_xmlChar_ptr 5
262
263static const xmlChar *gen_const_xmlChar_ptr(int no) {
264 if (no == 0) return((const xmlChar *) "foo");
265 if (no == 1) return((const xmlChar *) "<foo/>");
266 if (no == 2) return((const xmlChar *) "nøne");
267 if (no == 3) return((const xmlChar *) " 2ab ");
268 return(NULL);
269}
270static void des_const_xmlChar_ptr(int no ATTRIBUTE_UNUSED, const xmlChar *val ATTRIBUTE_UNUSED) {
271}
272
273#define gen_nb_filepath 8
274
275static const char *gen_filepath(int no) {
276 if (no == 0) return("missing.xml");
277 if (no == 1) return("<foo/>");
278 if (no == 2) return("test/ent2");
279 if (no == 3) return("test/valid/REC-xml-19980210.xml");
280 if (no == 4) return("test/valid/xhtml1-strict.dtd");
281 if (no == 5) return("http://missing.example.org/");
282 if (no == 6) return("http://missing. example.org/");
283 return(NULL);
284}
285static void des_filepath(int no ATTRIBUTE_UNUSED, const char *val ATTRIBUTE_UNUSED) {
286}
287
288#define gen_nb_fileoutput 6
289
290static const char *gen_fileoutput(int no) {
291 if (no == 0) return("/missing.xml");
292 if (no == 1) return("<foo/>");
293 if (no == 2) return("ftp://missing.example.org/foo");
294 if (no == 3) return("http://missing.example.org/");
295 if (no == 4) return("http://missing. example.org/");
296 return(NULL);
297}
298static void des_fileoutput(int no ATTRIBUTE_UNUSED, const char *val ATTRIBUTE_UNUSED) {
299}
300
301#define gen_nb_xmlParserCtxtPtr 2
302static xmlParserCtxtPtr gen_xmlParserCtxtPtr(int no) {
303 if (no == 0) return(xmlNewParserCtxt());
304 return(NULL);
305}
306static void des_xmlParserCtxtPtr(int no ATTRIBUTE_UNUSED, xmlParserCtxtPtr val) {
307 if (val != NULL)
308 xmlFreeParserCtxt(val);
309}
310
Daniel Veillarda03e3652004-11-02 18:45:30 +0000311#define gen_nb_xmlDocPtr 3
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000312static xmlDocPtr gen_xmlDocPtr(int no) {
313 if (no == 0) return(xmlNewDoc(BAD_CAST "1.0"));
Daniel Veillarda03e3652004-11-02 18:45:30 +0000314 if (no == 1) return(xmlReadMemory("<foo/>", 6, "test", NULL, 0));
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000315 return(NULL);
316}
317static void des_xmlDocPtr(int no ATTRIBUTE_UNUSED, xmlDocPtr val) {
318 if (val != NULL)
319 xmlFreeDoc(val);
320}
321
Daniel Veillarda03e3652004-11-02 18:45:30 +0000322#define gen_nb_xmlNodePtr 2
323static xmlNodePtr gen_xmlNodePtr(int no) {
324 if (no == 0) return(xmlNewPI(BAD_CAST "test", NULL));
325 return(NULL);
326}
327static void des_xmlNodePtr(int no ATTRIBUTE_UNUSED, xmlNodePtr val) {
328 if (val != NULL) {
329 xmlUnlinkNode(val);
330 xmlFreeNode(val);
331 }
332}
333
334#define gen_nb_xmlNodePtr_in 3
335static xmlNodePtr gen_xmlNodePtr_in(int no) {
336 if (no == 0) return(xmlNewPI(BAD_CAST "test", NULL));
337 if (no == 0) return(xmlNewText(BAD_CAST "text"));
338 return(NULL);
339}
340static void des_xmlNodePtr_in(int no ATTRIBUTE_UNUSED, xmlNodePtr val ATTRIBUTE_UNUSED) {
341}
342
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000343""");
344
345#
346# Provide the type destructors for the return values
347#
348
349known_return_types = [ "int", "const_char_ptr", "xmlDocPtr", "xmlNodePtr" ];
350
351def is_known_return_type(name):
352 for type in known_return_types:
353 if type == name:
354 return 1
355 return 0
356
357test.write("""
358static void desret_int(int val ATTRIBUTE_UNUSED) {
359}
360static void desret_const_char_ptr(const char *val ATTRIBUTE_UNUSED) {
361}
362static void desret_xmlDocPtr(xmlDocPtr val) {
363 xmlFreeDoc(val);
364}
365static void desret_xmlNodePtr(xmlNodePtr val) {
366 xmlUnlinkNode(val);
367 xmlFreeNode(val);
368}
369""");
370
371#
372# Generate the top caller
373#
374
375test.write("""
376/**
377 * testlibxml2:
378 *
379 * Main entry point of the tester for the full libxml2 module,
380 * it calls all the tester entry point for each module.
381 *
382 * Returns the number of error found
383 */
384static int
385testlibxml2(void)
386{
387 int ret = 0;
388
389""")
390
391for module in modules:
392 test.write(" ret += test_%s();\n" % module)
393
394test.write("""
395 printf("Total: %d tests, %d errors\\n", call_tests, ret);
396 return(ret);
397}
398
399""")
400
401#
402# How to handle a function
403#
404nb_tests = 0
405
406def generate_test(module, node):
407 global test
408 global nb_tests
409 nb_cond = 0
410 no_gen = 0
411
412 name = node.xpathEval('string(@name)')
413 if is_skipped_function(name):
414 return
415
416 test.write("""
417static int
418test_%s(void) {
419 int ret = 0;
420
421""" % (name))
422
423 #
424 # check we know how to handle the args and return values
425 # and store the informations for the generation
426 #
427 try:
428 args = node.xpathEval("arg")
429 except:
430 args = []
431 t_args = []
Daniel Veillarda03e3652004-11-02 18:45:30 +0000432 n = 0
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000433 for arg in args:
Daniel Veillarda03e3652004-11-02 18:45:30 +0000434 n = n + 1
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000435 rtype = arg.xpathEval("string(@type)")
436 if rtype == 'void':
437 break;
438 info = arg.xpathEval("string(@info)")
439 nam = arg.xpathEval("string(@name)")
Daniel Veillarda03e3652004-11-02 18:45:30 +0000440 type = type_convert(rtype, nam, info, module, name, n)
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000441 if is_known_param_type(type) == 0:
442 add_missing_type(type, name);
443 no_gen = 1
444 t_args.append((nam, type, rtype, info))
445
446 try:
447 rets = node.xpathEval("return")
448 except:
449 rets = []
450 t_ret = None
451 for ret in rets:
452 rtype = ret.xpathEval("string(@type)")
453 info = ret.xpathEval("string(@info)")
Daniel Veillarda03e3652004-11-02 18:45:30 +0000454 type = type_convert(rtype, 'return', info, module, name, 0)
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000455 if rtype == 'void':
456 break
457 if is_known_return_type(type) == 0:
458 add_missing_type(type, name);
459 no_gen = 1
460 t_ret = (type, rtype, info)
461 break
462
463 if no_gen == 1:
464 test.write("""
465 /* missing type support */
466 return(ret);
467}
468
469""")
470 return
471
472 try:
473 conds = node.xpathEval("cond")
474 for cond in conds:
475 test.write("#ifdef %s\n" % (cond.get_content()))
476 nb_cond = nb_cond + 1
477 except:
478 pass
479
480 # Declare the memory usage counter
481 no_mem = is_skipped_memcheck(name)
482 if no_mem == 0:
483 test.write(" int mem_base;\n");
484
485 # Declare the return value
486 if t_ret != None:
487 test.write(" %s ret_val;\n" % (t_ret[1]))
488
489 # Declare the arguments
490 for arg in t_args:
491 (nam, type, rtype, info) = arg;
492 # add declaration
493 test.write(" %s %s; /* %s */\n" % (rtype, nam, info))
494 test.write(" int n_%s;\n" % (nam))
495 test.write("\n")
496
497 # Cascade loop on of each argument list of values
498 for arg in t_args:
499 (nam, type, rtype, info) = arg;
500 #
501 test.write(" for (n_%s = 0;n_%s < gen_nb_%s;n_%s++) {\n" % (
502 nam, nam, type, nam))
503
504 # log the memory usage
505 if no_mem == 0:
506 test.write(" mem_base = xmlMemBlocks();\n");
507
508 # prepare the call
509 for arg in t_args:
510 (nam, type, rtype, info) = arg;
511 #
512 test.write(" %s = gen_%s(n_%s);\n" % (nam, type, nam))
513
514 # do the call, and clanup the result
515 if t_ret != None:
516 test.write("\n ret_val = %s(" % (name))
517 need = 0
518 for arg in t_args:
519 (nam, type, rtype, info) = arg
520 if need:
521 test.write(", ")
522 else:
523 need = 1
524 test.write("%s" % nam);
525 test.write(");\n desret_%s(ret_val);\n" % t_ret[0])
526 else:
527 test.write("\n %s(" % (name));
528 need = 0;
529 for arg in t_args:
530 (nam, type, rtype, info) = arg;
531 if need:
532 test.write(", ")
533 else:
534 need = 1
535 test.write("%s" % nam)
536 test.write(");\n")
537 test.write(" call_tests++;\n");
538
Daniel Veillarda03e3652004-11-02 18:45:30 +0000539 if extra_post_call.has_key(name):
540 test.write(" %s\n"% (extra_post_call[name]))
541
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000542 # Free the arguments
543 for arg in t_args:
544 (nam, type, rtype, info) = arg;
545 #
546 test.write(" des_%s(n_%s, %s);\n" % (type, nam, nam))
547
548 test.write(" xmlResetLastError();\n");
549 # Check the memory usage
550 if no_mem == 0:
551 test.write(""" if (mem_base != xmlMemBlocks()) {
Daniel Veillarda03e3652004-11-02 18:45:30 +0000552 printf("Leak of %%d blocks found in %s",
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000553 xmlMemBlocks() - mem_base);
554 ret++;
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000555""" % (name));
Daniel Veillarda03e3652004-11-02 18:45:30 +0000556 for arg in t_args:
557 (nam, type, rtype, info) = arg;
558 test.write(""" printf(" %%d", n_%s);\n""" % (nam))
559 test.write(""" printf("\\n");\n""")
560 test.write(" }\n")
Daniel Veillard36e5cd52004-11-02 14:52:23 +0000561
562 for arg in t_args:
563 test.write(" }\n")
564
565 #
566 # end of conditional
567 #
568 while nb_cond > 0:
569 test.write("#endif\n")
570 nb_cond = nb_cond -1
571
572 nb_tests = nb_tests + 1;
573
574 test.write("""
575 return(ret);
576}
577
578""")
579
580#
581# Generate all module callers
582#
583for module in modules:
584 # gather all the functions exported by that module
585 try:
586 functions = ctxt.xpathEval("/api/symbols/function[@file='%s']" % (module))
587 except:
588 print "Failed to gather functions from module %s" % (module)
589 continue;
590
591 # iterate over all functions in the module generating the test
592 for function in functions:
593 generate_test(module, function);
594
595 # header
596 test.write("""static int
597test_%s(void) {
598 int ret = 0;
599
600 printf("Testing %s ...\\n");
601""" % (module, module))
602
603 # iterate over all functions in the module generating the call
604 for function in functions:
605 name = function.xpathEval('string(@name)')
606 if is_skipped_function(name):
607 continue
608 test.write(" ret += test_%s();\n" % (name))
609
610 # footer
611 test.write("""
612 if (ret != 0)
613 printf("Module %s: %%d errors\\n", ret);
614 return(ret);
615}
616""" % (module))
617
618print "Generated test for %d modules and %d functions" %(len(modules), nb_tests)
619nr = 0
620miss = 'none'
621for missing in missing_types.keys():
622 n = len(missing_types[missing])
623 if n > nr:
624 miss = missing
625 nr = n
626
627if nr > 0:
628 print "most needed type support: %s %d times" % (miss, nr)
629
630