From eaaae2c0f77fe371b1da8c2c248888103d488961 Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Thu, 16 May 2024 12:13:35 -0400 Subject: First pass at test result creation, and optional markdown->html conversion --- src/junitxml.c | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 src/junitxml.c (limited to 'src/junitxml.c') diff --git a/src/junitxml.c b/src/junitxml.c new file mode 100644 index 0000000..df0514b --- /dev/null +++ b/src/junitxml.c @@ -0,0 +1,217 @@ +#include +#include +#include "strlist.h" +#include "junitxml.h" + +static void testcase_result_state_free(struct JUNIT_Testcase **testcase) { + struct JUNIT_Testcase *tc = (*testcase); + if (tc->tc_result_state_type == JUNIT_RESULT_STATE_FAILURE) { + guard_free(tc->result_state.failure->message); + guard_free(tc->result_state.failure); + } else if (tc->tc_result_state_type == JUNIT_RESULT_STATE_SKIPPED) { + guard_free(tc->result_state.skipped->message); + guard_free(tc->result_state.skipped); + } +} + +static void testcase_free(struct JUNIT_Testcase **testcase) { + struct JUNIT_Testcase *tc = (*testcase); + guard_free(tc->name); + guard_free(tc->message); + guard_free(tc->classname); + testcase_result_state_free(&tc); + guard_free(tc); +} + +void junitxml_testsuite_free(struct JUNIT_Testsuite **testsuite) { + struct JUNIT_Testsuite *suite = (*testsuite); + guard_free(suite->name); + guard_free(suite->hostname); + guard_free(suite->timestamp); + for (size_t i = 0; i < suite->_tc_alloc; i++) { + testcase_free(&suite->testcase[i]); + } + guard_free(suite); +} + +static int testsuite_append_testcase(struct JUNIT_Testsuite **testsuite, struct JUNIT_Testcase *testcase) { + struct JUNIT_Testsuite *suite = (*testsuite); + struct JUNIT_Testcase **tmp = realloc(suite->testcase, (suite->_tc_alloc + 1 ) * sizeof(*testcase)); + if (!tmp) { + return -1; + } else if (tmp != suite->testcase) { + suite->testcase = tmp; + } + suite->testcase[suite->_tc_inuse] = testcase; + suite->_tc_inuse++; + suite->_tc_alloc++; + return 0; +} + +static struct JUNIT_Failure *testcase_failure_from_attributes(struct StrList *attrs) { + struct JUNIT_Failure *result; + + result = calloc(1, sizeof(*result)); + if(!result) { + return NULL; + } + for (size_t x = 0; x < strlist_count(attrs); x += 2) { + char *attr_name = strlist_item(attrs, x); + char *attr_value = strlist_item(attrs, x + 1); + if (!strcmp(attr_name, "message")) { + result->message = strdup(attr_value); + } + } + return result; +} + +static struct JUNIT_Skipped *testcase_skipped_from_attributes(struct StrList *attrs) { + struct JUNIT_Skipped *result; + + result = calloc(1, sizeof(*result)); + if(!result) { + return NULL; + } + for (size_t x = 0; x < strlist_count(attrs); x += 2) { + char *attr_name = strlist_item(attrs, x); + char *attr_value = strlist_item(attrs, x + 1); + if (!strcmp(attr_name, "message")) { + result->message = strdup(attr_value); + } + } + return result; +} + +static struct JUNIT_Testcase *testcase_from_attributes(struct StrList *attrs) { + struct JUNIT_Testcase *result; + + result = calloc(1, sizeof(*result)); + if(!result) { + return NULL; + } + for (size_t x = 0; x < strlist_count(attrs); x += 2) { + char *attr_name = strlist_item(attrs, x); + char *attr_value = strlist_item(attrs, x + 1); + if (!strcmp(attr_name, "name")) { + result->name = strdup(attr_value); + } else if (!strcmp(attr_name, "classname")) { + result->classname = strdup(attr_value); + } else if (!strcmp(attr_name, "time")) { + result->time = strtof(attr_value, NULL); + } else if (!strcmp(attr_name, "message")) { + result->message = strdup(attr_value); + } + } + return result; +} + +static struct StrList *attributes_to_strlist(xmlTextReaderPtr reader) { + struct StrList *list; + xmlNodePtr node = xmlTextReaderCurrentNode(reader); + if (!node) { + return NULL; + } + + list = strlist_init(); + if (xmlTextReaderNodeType(reader) == 1 && node->properties) { + xmlAttr *attr = node->properties; + while (attr && attr->name && attr->children) { + char *attr_name = (char *) attr->name; + char *attr_value = (char *) xmlNodeListGetString(node->doc, attr->children, 1); + strlist_append(&list, attr_name ? attr_name : ""); + strlist_append(&list, attr_value ? attr_value : ""); + xmlFree((xmlChar *) attr_value); + attr = attr->next; + } + } + return list; +} + +static int read_xml_data(xmlTextReaderPtr reader, struct JUNIT_Testsuite **testsuite) { + const xmlChar *name; + const xmlChar *value; + + name = xmlTextReaderConstName(reader); + if (!name) { + name = BAD_CAST "--"; + } + value = xmlTextReaderConstValue(reader); + const char *node_name = (char *) name; + const char *node_value = (char *) value; + + struct StrList *attrs = attributes_to_strlist(reader); + if (attrs && strlist_count(attrs)) { + if (!strcmp(node_name, "testsuite")) { + for (size_t x = 0; x < strlist_count(attrs); x += 2) { + char *attr_name = strlist_item(attrs, x); + char *attr_value = strlist_item(attrs, x + 1); + if (!strcmp(attr_name, "name")) { + (*testsuite)->name = strdup(attr_value); + } else if (!strcmp(attr_name, "errors")) { + (*testsuite)->errors = (int) strtol(attr_value, NULL, 10); + } else if (!strcmp(attr_name, "failures")) { + (*testsuite)->failures = (int) strtol(attr_value, NULL, 0); + } else if (!strcmp(attr_name, "skipped")) { + (*testsuite)->skipped = (int) strtol(attr_value, NULL, 0); + } else if (!strcmp(attr_name, "tests")) { + (*testsuite)->tests = (int) strtol(attr_value, NULL, 0); + } else if (!strcmp(attr_name, "time")) { + (*testsuite)->time = strtof(attr_value, NULL); + } else if (!strcmp(attr_name, "timestamp")) { + (*testsuite)->timestamp = strdup(attr_value); + } else if (!strcmp(attr_name, "hostname")) { + (*testsuite)->hostname = strdup(attr_value); + } + } + } else if (!strcmp(node_name, "testcase")) { + struct JUNIT_Testcase *testcase = testcase_from_attributes(attrs); + testsuite_append_testcase(testsuite, testcase); + } else if (!strcmp(node_name, "failure")) { + size_t cur_tc = (*testsuite)->_tc_inuse > 0 ? (*testsuite)->_tc_inuse - 1 : (*testsuite)->_tc_inuse; + struct JUNIT_Failure *failure = testcase_failure_from_attributes(attrs); + (*testsuite)->testcase[cur_tc]->tc_result_state_type = JUNIT_RESULT_STATE_FAILURE; + (*testsuite)->testcase[cur_tc]->result_state.failure = failure; + } else if (!strcmp(node_name, "skipped")) { + size_t cur_tc = (*testsuite)->_tc_inuse > 0 ? (*testsuite)->_tc_inuse - 1 : (*testsuite)->_tc_inuse; + struct JUNIT_Skipped *skipped = testcase_skipped_from_attributes(attrs); + (*testsuite)->testcase[cur_tc]->tc_result_state_type = JUNIT_RESULT_STATE_SKIPPED; + (*testsuite)->testcase[cur_tc]->result_state.skipped = skipped; + } + } + guard_strlist_free(&attrs); + return 0; +} + +static int read_xml_file(const char *filename, struct JUNIT_Testsuite **testsuite) { + xmlTextReaderPtr reader; + int result; + + reader = xmlReaderForFile(filename, NULL, 0); + if (!reader) { + return -1; + } + + result = xmlTextReaderRead(reader); + while (result == 1) { + read_xml_data(reader, testsuite); + result = xmlTextReaderRead(reader); + } + + xmlFreeTextReader(reader); + return 0; +} + +struct JUNIT_Testsuite *junitxml_testsuite_read(const char *filename) { + struct JUNIT_Testsuite *result; + + if (access(filename, F_OK)) { + return NULL; + } + + result = calloc(1, sizeof(*result)); + if (!result) { + return NULL; + } + read_xml_file(filename, &result); + return result; +} \ No newline at end of file -- cgit