| #!/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 <stdio.h> |
| #include <libxml/xmlerror.h> |
| |
| 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 <libxml/%s.h>\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("<foo/>"); |
| 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 *) "<foo/>"); |
| 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("<foo/>"); |
| 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("<foo/>"); |
| 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("<foo/>", 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) |
| |
| |