aboutsummaryrefslogtreecommitdiff
path: root/src/junitxml.c
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2024-05-16 12:13:35 -0400
committerJoseph Hunkeler <jhunkeler@gmail.com>2024-05-16 12:17:46 -0400
commiteaaae2c0f77fe371b1da8c2c248888103d488961 (patch)
tree1137d0e51c214fb9aec6f321c40250a49c6fdba9 /src/junitxml.c
parent9ad649be44c568a00f2f407d715d07cd585c2b25 (diff)
downloadstasis-eaaae2c0f77fe371b1da8c2c248888103d488961.tar.gz
First pass at test result creation, and optional markdown->html conversion
Diffstat (limited to 'src/junitxml.c')
-rw-r--r--src/junitxml.c217
1 files changed, 217 insertions, 0 deletions
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 <stdlib.h>
+#include <string.h>
+#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