#!/usr/bin/python -u # # generate a tester program for the API # import sys import string try: import libxml2 except: print "libxml2 python bindings not available, skipping testapi.c generation" sys.exit(0) # # Modules we don't want skip in API test # skipped_modules = [ "SAX", "SAX2", "xlink", "threads", "globals", "xpathInternals", "xmlunicode", "parserInternals", "xmlmemory", "xmlversion", "debugXML", "xmlexports" ] # # Some function really need to be skipped for the tests. # skipped_functions = [ # block on I/O "xmlFdRead", "xmlReadFd", "xmlCtxtReadFd", "htmlFdRead", "htmlReadFd", "htmlCtxtReadFd", "xmlReaderNewFd", # library state cleanup, generate false leak informations and other # troubles, heavillyb tested otherwise. "xmlCleanupParser", "xmlRelaxNGCleanupTypes", # hard to avoid leaks in the tests "xmlStrcat", "xmlStrncat", # unimplemented "xmlTextReaderReadInnerXml", "xmlTextReaderReadOuterXml", "xmlTextReaderReadString" ] # # Those functions have side effect on the global state # and hence generate errors on memory allocation tests # skipped_memcheck = [ "xmlLoadCatalog", "xmlAddEncodingAlias", "xmlSchemaInitTypes", "xmlNanoFTPProxy", "xmlNanoFTPScanProxy", "xmlNanoHTTPScanProxy", "xmlResetLastError", "xmlCatalogConvert", "xmlCatalogRemove", "xmlLoadCatalogs", "xmlCleanupCharEncodingHandlers", "xmlInitCharEncodingHandlers", "xmlCatalogCleanup", "htmlParseFile" # loads the catalogs ] # # Extra code needed for some test cases # extra_post_call = { "xmlAddChild": "if (ret_val == NULL) { xmlFreeNode(cur) ; cur = NULL ; }", "xmlAddChildList": "if (ret_val == NULL) { xmlFreeNodeList(cur) ; cur = NULL ; }", "xmlAddSibling": "if (ret_val == NULL) { xmlFreeNode(elem) ; elem = NULL ; }", "xmlAddNextSibling": "if (ret_val == NULL) { xmlFreeNode(elem) ; elem = NULL ; }", "xmlAddPrevSibling": "if (ret_val == NULL) { xmlFreeNode(elem) ; elem = NULL ; }", "xmlDocSetRootElement": "if (doc == NULL) { xmlFreeNode(root) ; root = NULL ; }", "xmlReplaceNode": """if ((old == NULL) || (old->parent == NULL)) { xmlFreeNode(cur) ; cur = NULL ; }""", "xmlTextMerge": """if ((first != NULL) && (first->type != XML_TEXT_NODE)) { xmlFreeNode(second) ; second = NULL ; }""", "xmlBuildQName": """if ((ret_val != NULL) && (ret_val != ncname) && (ret_val != prefix) && (ret_val != memory)) xmlFree(ret_val); ret_val = NULL;""", } modules = [] def is_skipped_module(name): for mod in skipped_modules: if mod == name: return 1 return 0 def is_skipped_function(name): for fun in skipped_functions: if fun == name: return 1 # Do not test destructors if string.find(name, 'Free') != -1: return 1 return 0 def is_skipped_memcheck(name): for fun in skipped_memcheck: if fun == name: return 1 return 0 missing_types = {} def add_missing_type(name, func): try: list = missing_types[name] list.append(func) except: missing_types[name] = [func] # # Open the input API description and the C test program result # doc = libxml2.readFile('doc/libxml2-api.xml', None, 0) if doc == None: print "Failed to load doc/libxml2-api.xml" sys.exit(1) test = open('testapi.c', 'w') ctxt = doc.xpathNewContext() headers = ctxt.xpathEval("/api/files/file") # # Generate the test header # test.write("""/* * testapi.c: libxml2 API tester program. * * Automatically generated by gentest.py from libxml2-api.xml * * See Copyright for the status of this software. * * daniel@veillard.com */ #include #include static int testlibxml2(void); static int generic_errors = 0; static int call_tests = 0; static xmlChar chartab[1024] = " chartab\\n"; static void structured_errors(void *userData ATTRIBUTE_UNUSED, xmlErrorPtr error ATTRIBUTE_UNUSED) { generic_errors++; } int main(void) { int ret; int blocks, mem; xmlInitParser(); xmlRelaxNGInitTypes(); LIBXML_TEST_VERSION xmlSetStructuredErrorFunc(NULL, structured_errors); ret = testlibxml2(); xmlCleanupParser(); blocks = xmlMemBlocks(); mem = xmlMemUsed(); if ((blocks != 0) || (mem != 0)) { printf("testapi leaked %d bytes in %d blocks\\n", mem, blocks); } xmlMemoryDump(); return (ret != 0); } """); # # Load the interfaces # for file in headers: name = file.xpathEval('string(@name)') if (name == None) or (name == ''): continue # # do not test deprecated APIs # desc = file.xpathEval('string(description)') if string.find(desc, 'DEPRECATED') != -1: print "Skipping deprecated interface %s" % name continue; # # Some module may be skipped because they don't really consists # of user callable APIs # if is_skipped_module(name): continue test.write("#include \n" % name) modules.append(name) # # Generate the callers signatures # for module in modules: test.write("static int test_%s(void);\n" % module); # # Provide the type generators and destructors for the parameters # def type_convert(str, name, info, module, function, pos): res = string.replace(str, " *", "_ptr") res = string.replace(res, " ", "_") res = string.replace(res, "htmlNode", "xmlNode") res = string.replace(res, "htmlDoc", "xmlDoc") res = string.replace(res, "htmlParser", "xmlParser") if res == 'const_char_ptr': if string.find(name, "file") != -1 or \ string.find(name, "uri") != -1 or \ string.find(name, "URI") != -1 or \ string.find(info, "filename") != -1 or \ string.find(info, "URI") != -1 or \ string.find(info, "URL") != -1: if string.find(function, "Save") != -1: return('fileoutput') return('filepath') if res == 'void_ptr': if module == 'nanoftp' and name == 'ctx': return('xmlNanoFTPCtxtPtr') if module == 'nanohttp' and name == 'ctx': return('xmlNanoHTTPCtxtPtr') if string.find(name, "data") != -1: return('userdata'); if res == 'xmlNodePtr' and pos != 0: if (function == 'xmlAddChild' and pos == 2) or \ (function == 'xmlAddChildList' and pos == 2) or \ (function == 'xmlAddNextSibling' and pos == 2) or \ (function == 'xmlAddSibling' and pos == 2) or \ (function == 'xmlDocSetRootElement' and pos == 2) or \ (function == 'xmlReplaceNode' and pos == 2) or \ (function == 'xmlTextMerge') or \ (function == 'xmlAddPrevSibling' and pos == 2): return('xmlNodePtr_in'); return res known_param_types = [ "int", "const_char_ptr", "const_xmlChar_ptr", "xmlParserCtxtPtr", "xmlDocPtr", "filepath", "fileoutput", "xmlNodePtr", "xmlNodePtr_in", "userdata", "xmlChar_ptr", "xmlTextWriterPtr", "xmlTextReaderPtr" ]; def is_known_param_type(name): for type in known_param_types: if type == name: return 1 return 0 test.write(""" #define gen_nb_userdata 3 static void *gen_userdata(int no) { if (no == 0) return((void *) &call_tests); if (no == 1) return((void *) -1); return(NULL); } static void des_userdata(int no ATTRIBUTE_UNUSED, void *val ATTRIBUTE_UNUSED) { } #define gen_nb_int 4 static int gen_int(int no) { if (no == 0) return(0); if (no == 1) return(1); if (no == 2) return(122); return(-1); } static void des_int(int no ATTRIBUTE_UNUSED, int val ATTRIBUTE_UNUSED) { } #define gen_nb_const_char_ptr 4 static const char *gen_const_char_ptr(int no) { if (no == 0) return("foo"); if (no == 1) return(""); if (no == 2) return("test/ent2"); return(NULL); } static void des_const_char_ptr(int no ATTRIBUTE_UNUSED, const char *val ATTRIBUTE_UNUSED) { } #define gen_nb_xmlChar_ptr 2 static xmlChar *gen_xmlChar_ptr(int no) { if (no == 0) return(&chartab); return(NULL); } static void des_xmlChar_ptr(int no ATTRIBUTE_UNUSED, xmlChar *val ATTRIBUTE_UNUSED) { } #define gen_nb_const_xmlChar_ptr 5 static const xmlChar *gen_const_xmlChar_ptr(int no) { if (no == 0) return((const xmlChar *) "foo"); if (no == 1) return((const xmlChar *) ""); if (no == 2) return((const xmlChar *) "nøne"); if (no == 3) return((const xmlChar *) " 2ab "); return(NULL); } static void des_const_xmlChar_ptr(int no ATTRIBUTE_UNUSED, const xmlChar *val ATTRIBUTE_UNUSED) { } #define gen_nb_filepath 8 static const char *gen_filepath(int no) { if (no == 0) return("missing.xml"); if (no == 1) return(""); if (no == 2) return("test/ent2"); if (no == 3) return("test/valid/REC-xml-19980210.xml"); if (no == 4) return("test/valid/xhtml1-strict.dtd"); if (no == 5) return("http://missing.example.org/"); if (no == 6) return("http://missing. example.org/"); return(NULL); } static void des_filepath(int no ATTRIBUTE_UNUSED, const char *val ATTRIBUTE_UNUSED) { } #define gen_nb_fileoutput 6 static const char *gen_fileoutput(int no) { if (no == 0) return("/missing.xml"); if (no == 1) return(""); if (no == 2) return("ftp://missing.example.org/foo"); if (no == 3) return("http://missing.example.org/"); if (no == 4) return("http://missing. example.org/"); return(NULL); } static void des_fileoutput(int no ATTRIBUTE_UNUSED, const char *val ATTRIBUTE_UNUSED) { } #define gen_nb_xmlParserCtxtPtr 2 static xmlParserCtxtPtr gen_xmlParserCtxtPtr(int no) { if (no == 0) return(xmlNewParserCtxt()); return(NULL); } static void des_xmlParserCtxtPtr(int no ATTRIBUTE_UNUSED, xmlParserCtxtPtr val) { if (val != NULL) xmlFreeParserCtxt(val); } #define gen_nb_xmlDocPtr 3 static xmlDocPtr gen_xmlDocPtr(int no) { if (no == 0) return(xmlNewDoc(BAD_CAST "1.0")); if (no == 1) return(xmlReadMemory("", 6, "test", NULL, 0)); return(NULL); } static void des_xmlDocPtr(int no ATTRIBUTE_UNUSED, xmlDocPtr val) { if (val != NULL) xmlFreeDoc(val); } #define gen_nb_xmlNodePtr 2 static xmlNodePtr gen_xmlNodePtr(int no) { if (no == 0) return(xmlNewPI(BAD_CAST "test", NULL)); return(NULL); } static void des_xmlNodePtr(int no ATTRIBUTE_UNUSED, xmlNodePtr val) { if (val != NULL) { xmlUnlinkNode(val); xmlFreeNode(val); } } #define gen_nb_xmlNodePtr_in 3 static xmlNodePtr gen_xmlNodePtr_in(int no) { if (no == 0) return(xmlNewPI(BAD_CAST "test", NULL)); if (no == 0) return(xmlNewText(BAD_CAST "text")); return(NULL); } static void des_xmlNodePtr_in(int no ATTRIBUTE_UNUSED, xmlNodePtr val ATTRIBUTE_UNUSED) { } #define gen_nb_xmlTextWriterPtr 2 static xmlTextWriterPtr gen_xmlTextWriterPtr(int no) { if (no == 0) return(xmlNewTextWriterFilename("test.out", 0)); return(NULL); } static void des_xmlTextWriterPtr(int no ATTRIBUTE_UNUSED, xmlTextWriterPtr val) { if (val != NULL) xmlFreeTextWriter(val); } #define gen_nb_xmlTextReaderPtr 4 static xmlTextReaderPtr gen_xmlTextReaderPtr(int no) { if (no == 0) return(xmlNewTextReaderFilename("test/ent2")); if (no == 1) return(xmlNewTextReaderFilename("test/valid/REC-xml-19980210.xml")); if (no == 2) return(xmlNewTextReaderFilename("test/valid/dtds/xhtml1-strict.dtd")); return(NULL); } static void des_xmlTextReaderPtr(int no ATTRIBUTE_UNUSED, xmlTextReaderPtr val) { if (val != NULL) xmlFreeTextReader(val); } """); # # Provide the type destructors for the return values # known_return_types = [ "int", "const_char_ptr", "xmlDocPtr", "xmlNodePtr", "xmlChar_ptr" ]; def is_known_return_type(name): for type in known_return_types: if type == name: return 1 return 0 test.write(""" static void desret_int(int val ATTRIBUTE_UNUSED) { } static void desret_const_char_ptr(const char *val ATTRIBUTE_UNUSED) { } static void desret_xmlChar_ptr(xmlChar *val) { if (val != NULL) xmlFree(val); } static void desret_xmlDocPtr(xmlDocPtr val) { xmlFreeDoc(val); } static void desret_xmlNodePtr(xmlNodePtr val) { xmlUnlinkNode(val); xmlFreeNode(val); } """); # # Generate the top caller # test.write(""" /** * testlibxml2: * * Main entry point of the tester for the full libxml2 module, * it calls all the tester entry point for each module. * * Returns the number of error found */ static int testlibxml2(void) { int ret = 0; """) for module in modules: test.write(" ret += test_%s();\n" % module) test.write(""" printf("Total: %d tests, %d errors\\n", call_tests, ret); return(ret); } """) # # How to handle a function # nb_tests = 0 def generate_test(module, node): global test global nb_tests nb_cond = 0 no_gen = 0 name = node.xpathEval('string(@name)') if is_skipped_function(name): return test.write(""" static int test_%s(void) { int ret = 0; """ % (name)) # # check we know how to handle the args and return values # and store the informations for the generation # try: args = node.xpathEval("arg") except: args = [] t_args = [] n = 0 for arg in args: n = n + 1 rtype = arg.xpathEval("string(@type)") if rtype == 'void': break; info = arg.xpathEval("string(@info)") nam = arg.xpathEval("string(@name)") type = type_convert(rtype, nam, info, module, name, n) if is_known_param_type(type) == 0: add_missing_type(type, name); no_gen = 1 t_args.append((nam, type, rtype, info)) try: rets = node.xpathEval("return") except: rets = [] t_ret = None for ret in rets: rtype = ret.xpathEval("string(@type)") info = ret.xpathEval("string(@info)") type = type_convert(rtype, 'return', info, module, name, 0) if rtype == 'void': break if is_known_return_type(type) == 0: add_missing_type(type, name); no_gen = 1 t_ret = (type, rtype, info) break if no_gen == 1: test.write(""" /* missing type support */ return(ret); } """) return try: conds = node.xpathEval("cond") for cond in conds: test.write("#ifdef %s\n" % (cond.get_content())) nb_cond = nb_cond + 1 except: pass # Declare the memory usage counter no_mem = is_skipped_memcheck(name) if no_mem == 0: test.write(" int mem_base;\n"); # Declare the return value if t_ret != None: test.write(" %s ret_val;\n" % (t_ret[1])) # Declare the arguments for arg in t_args: (nam, type, rtype, info) = arg; # add declaration test.write(" %s %s; /* %s */\n" % (rtype, nam, info)) test.write(" int n_%s;\n" % (nam)) test.write("\n") # Cascade loop on of each argument list of values for arg in t_args: (nam, type, rtype, info) = arg; # test.write(" for (n_%s = 0;n_%s < gen_nb_%s;n_%s++) {\n" % ( nam, nam, type, nam)) # log the memory usage if no_mem == 0: test.write(" mem_base = xmlMemBlocks();\n"); # prepare the call for arg in t_args: (nam, type, rtype, info) = arg; # test.write(" %s = gen_%s(n_%s);\n" % (nam, type, nam)) # do the call, and clanup the result if t_ret != None: test.write("\n ret_val = %s(" % (name)) need = 0 for arg in t_args: (nam, type, rtype, info) = arg if need: test.write(", ") else: need = 1 test.write("%s" % nam); test.write(");\n") if extra_post_call.has_key(name): test.write(" %s\n"% (extra_post_call[name])) test.write(" desret_%s(ret_val);\n" % t_ret[0]) else: test.write("\n %s(" % (name)); need = 0; for arg in t_args: (nam, type, rtype, info) = arg; if need: test.write(", ") else: need = 1 test.write("%s" % nam) test.write(");\n") if extra_post_call.has_key(name): test.write(" %s\n"% (extra_post_call[name])) test.write(" call_tests++;\n"); # Free the arguments for arg in t_args: (nam, type, rtype, info) = arg; # test.write(" des_%s(n_%s, %s);\n" % (type, nam, nam)) test.write(" xmlResetLastError();\n"); # Check the memory usage if no_mem == 0: test.write(""" if (mem_base != xmlMemBlocks()) { printf("Leak of %%d blocks found in %s", xmlMemBlocks() - mem_base); ret++; """ % (name)); for arg in t_args: (nam, type, rtype, info) = arg; test.write(""" printf(" %%d", n_%s);\n""" % (nam)) test.write(""" printf("\\n");\n""") test.write(" }\n") for arg in t_args: test.write(" }\n") # # end of conditional # while nb_cond > 0: test.write("#endif\n") nb_cond = nb_cond -1 nb_tests = nb_tests + 1; test.write(""" return(ret); } """) # # Generate all module callers # for module in modules: # gather all the functions exported by that module try: functions = ctxt.xpathEval("/api/symbols/function[@file='%s']" % (module)) except: print "Failed to gather functions from module %s" % (module) continue; # iterate over all functions in the module generating the test for function in functions: generate_test(module, function); # header test.write("""static int test_%s(void) { int ret = 0; printf("Testing %s ...\\n"); """ % (module, module)) # iterate over all functions in the module generating the call for function in functions: name = function.xpathEval('string(@name)') if is_skipped_function(name): continue test.write(" ret += test_%s();\n" % (name)) # footer test.write(""" if (ret != 0) printf("Module %s: %%d errors\\n", ret); return(ret); } """ % (module)) print "Generated test for %d modules and %d functions" %(len(modules), nb_tests) nr = 0 miss = 'none' for missing in missing_types.keys(): n = len(missing_types[missing]) if n > nr: miss = missing nr = n if nr > 0: print "most needed type support: %s %d times" % (miss, nr)