From ac07ad67d964572ffb10f188a4602a447f654f93 Mon Sep 17 00:00:00 2001
From: Markus Kauppila <markus.kauppila@gmail.com>
Date: Tue, 21 Jun 2011 19:31:46 +0300
Subject: [PATCH] Creating flexible logging system which supports XML and plain
 text. Work under progress.

---
 test/test-automation/Makefile.am  |   5 +
 test/test-automation/logger.h     |  26 ++-
 test/test-automation/xml.c        | 254 +++++++++++++++++++++
 test/test-automation/xml.h        |  80 +++++++
 test/test-automation/xml_logger.c | 364 ++++--------------------------
 5 files changed, 411 insertions(+), 318 deletions(-)
 create mode 100644 test/test-automation/xml.c
 create mode 100644 test/test-automation/xml.h

diff --git a/test/test-automation/Makefile.am b/test/test-automation/Makefile.am
index 27b4941f..ffd0928f 100644
--- a/test/test-automation/Makefile.am
+++ b/test/test-automation/Makefile.am
@@ -7,6 +7,11 @@ runner_SOURCES = runner.c SDL_test.c
 runner_CLAGS = -W -Wall -Wextra -g `sdl-config --cflags` -DSDL_NO_COMPAT
 runner_LDFLAGS = `sdl-config --libs` 
 
+bin_PROGRAMS = logger
+logger_SOURCES = xml_logger.c xml.c
+logger_CLAGS = -W -Wall -Wextra -g `sdl-config --cflags` -DSDL_NO_COMPAT
+logger_LDFLAGS = `sdl-config --libs` 
+
 install: install-tests
 install-tests:
 	-mkdir tests
diff --git a/test/test-automation/logger.h b/test/test-automation/logger.h
index 160be93e..dae39379 100644
--- a/test/test-automation/logger.h
+++ b/test/test-automation/logger.h
@@ -21,6 +21,30 @@
 #ifndef _LOGGER_H
 #define _LOGGER_H
 
-// Put function pointers here
+#include <time.h>
+
+// Function pointer to function which handles to output
+typedef int (*LogOutputFp)(const char *);
+
+/*!
+ * Generic logger interface
+ *
+ */
+void RunStarted(LogOutputFp outputFn, const char *runnerParameters, time_t eventTime);
+void RunEnded(time_t endTime, time_t totalRuntime);
+
+void SuiteStarted(const char *suiteName, time_t eventTime);
+void SuiteEnded(int testsPassed, int testsFailed, int testsSkipped,
+                double endTime, time_t totalRuntime);
+
+void TestStarted(const char *testName, const char *testDescription, time_t startTime);
+void TestEnded(const char *testName, const char *testDescription, int testResult,
+               int numAsserts, time_t endTime, time_t totalRuntime);
+
+void Assert(const char *assertName, int assertResult, const char *assertMessage,
+            time_t eventTime);
+
+void Log(const char *logMessage, time_t eventTime);
+
 
 #endif
diff --git a/test/test-automation/xml.c b/test/test-automation/xml.c
new file mode 100644
index 00000000..bc76285c
--- /dev/null
+++ b/test/test-automation/xml.c
@@ -0,0 +1,254 @@
+/*
+  Copyright (C) 2011 Markus Kauppila <markus.kauppila@gmail.com>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+
+#include <stdio.h>
+//#include <stdlib.h>
+#include <string.h>
+//#include <stdarg.h>
+#include <assert.h>
+
+#include <SDL/SDL.h>
+
+#include "xml.h"
+
+/*! Points the function which handles the output */
+static LogOutputFp logger = 0;
+
+/*!
+ * Defines structure used for "counting" open XML-tags
+ */
+typedef struct TagList {
+	const char *tag;
+	struct TagList *next;
+} TagList;
+
+static TagList *openTags = NULL;
+
+/*!
+ * Prepend the open tags list
+ *
+ * \return On error returns non-zero value, otherwise zero will returned
+ */
+static int
+AddOpenTag(const char *tag)
+{
+	TagList *openTag = SDL_malloc(sizeof(TagList));
+	if(openTag == NULL) {
+		return 1;
+	}
+	memset(openTag, 0, sizeof(TagList));
+
+	openTag->tag = tag; // Should be fine without malloc?
+	openTag->next = openTags;
+
+	openTags = openTag;
+
+	return 0;
+}
+
+/*!
+ * Removes the first tag from the open tag list
+ *
+ * \return On error returns non-zero value, otherwise zero will returned
+ */
+static int
+RemoveOpenTag(const char *tag)
+{
+	if(openTags == NULL) {
+		return 1;
+	}
+
+	int retVal = 0;
+
+	// Tag should always be the same as previously opened tag
+	// It prevents opening and ending tag mismatch
+	if(SDL_strcmp(openTags->tag, tag) == 0) {
+		TagList *openTag = openTags;
+		openTags  = openTags->next;
+
+		free(openTag);
+	} else {
+		printf("Debug | RemoveOpenTag(): open/end tag mismatch");
+		retVal = 1;
+	}
+
+	return retVal;
+}
+
+/*!
+ * Debug function. Prints the contents of the open tags list.
+ */
+static void
+PrintOpenTags()
+{
+	printf("\nOpen tags:\n");
+
+	TagList *openTag = NULL;
+	for(openTag = openTags; openTag; openTag = openTag->next) {
+		printf("\ttag: %s\n", openTag->tag);
+	}
+}
+
+/*
+===================
+
+	XML
+
+===================
+*/
+
+static int has_open_element = 0;
+
+void
+XMLOpenDocument(const char *rootTag, LogOutputFp log)
+{
+	assert(log != NULL);
+	logger = log;
+
+	logger("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
+
+	size_t size = SDL_strlen(rootTag) + 3 + 1; /* one extra for '\0', '<' and '>' */
+	char *buffer = SDL_malloc(size);
+	snprintf(buffer, size, "%s%s%s", "<", rootTag, ">");
+	logger(buffer);
+	SDL_free(buffer);
+
+	// add open tag
+	AddOpenTag(rootTag);
+}
+
+void
+XMLCloseDocument() {
+	// Close the open tags with proper nesting
+	TagList *openTag = openTags;
+	while(openTag) {
+		TagList *temp = openTag->next;
+
+		size_t size = SDL_strlen(openTag->tag) + 4 + 1; /* one extra for '\0', '<', '/' and '>' */
+		char *buffer = SDL_malloc(size);
+		snprintf(buffer, size, "%s%s%s", "</", openTag->tag, ">");
+		logger(buffer);
+		SDL_free(buffer);
+
+		RemoveOpenTag(openTag->tag);
+
+		openTag = temp;
+	}
+}
+
+static const char *currentTag = NULL;
+
+void
+XMLOpenElement(const char *tag)
+{
+	size_t size = SDL_strlen(tag) + 2 + 1; /* one extra for '\0', '<' */
+	char *buffer = SDL_malloc(size);
+	snprintf(buffer, size, "%s%s%s", "<", tag, ">");
+	logger(buffer);
+	SDL_free(buffer);
+
+	currentTag = tag;
+
+	has_open_element = 1;
+
+	AddOpenTag(tag);
+}
+
+
+void
+XMLOpenElementWithAttribute(const char *tag, Attribute attribute)
+{
+	size_t size = SDL_strlen(tag) + 2 + 1; /* one extra for '\0', '<' */
+	char *buffer = SDL_malloc(size);
+
+	snprintf(buffer,  size, "%s%s", "<", tag);
+	logger(buffer);
+	SDL_free(buffer);
+
+	currentTag = tag;
+
+	has_open_element = 1;
+
+	AddOpenTag(tag);
+}
+
+//! \todo make this static and remove from interface?
+void
+XMLAddAttribute(const char *attribute, const char *value)
+{
+	// Requires open element
+	if(has_open_element == 0) {
+		return ;
+	}
+	size_t attributeSize = SDL_strlen(attribute);
+	size_t valueSize = SDL_strlen(value);
+
+	size_t size = 1 + attributeSize + 3 + valueSize + 1;
+	char *buffer = SDL_malloc(size); // 1 for '='
+	snprintf(buffer, size, " %s%s\"%s\"", attribute, "=", value);
+	logger(buffer);
+	SDL_free(buffer);
+}
+
+void
+XMLAddContent(const char *content)
+{
+	size_t size = SDL_strlen(content) + 1 + 1;
+	char *buffer = SDL_malloc(size);
+	snprintf(buffer, size, "%s", content);
+	logger(buffer);
+	SDL_free(buffer);}
+
+void
+XMLCloseElement(const char *tag)
+{
+	// Close the open tags with proper nesting. Closes tags until it finds
+	// the given tag which is the last tag that will be closed
+	TagList *openTag = openTags;
+	while(openTag) {
+		TagList *temp = openTag->next;
+
+		size_t size = SDL_strlen(openTag->tag) + 4 + 1; /* one extra for '\0', '<', '/' and '>' */
+		char *buffer = SDL_malloc(size);
+		snprintf(buffer, size, "%s%s%s", "</", openTag->tag, ">");
+		logger(buffer);
+		SDL_free(buffer);
+
+		const int openTagSize = SDL_strlen(openTag->tag);
+		const int tagSize = SDL_strlen(tag);
+		const int compSize = (openTagSize > tagSize) ? openTagSize : tagSize;
+
+		int breakOut = 0;
+		if(SDL_strncmp(openTag->tag, tag, compSize) == 0) {
+			breakOut = 1;
+		}
+
+		RemoveOpenTag(openTag->tag);
+
+		openTag = temp;
+
+		if(breakOut) {
+			break;
+		}
+	}
+
+	has_open_element = 0;
+}
diff --git a/test/test-automation/xml.h b/test/test-automation/xml.h
new file mode 100644
index 00000000..b222f9de
--- /dev/null
+++ b/test/test-automation/xml.h
@@ -0,0 +1,80 @@
+/*
+  Copyright (C) 2011 Markus Kauppila <markus.kauppila@gmail.com>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef _XML_H
+#define _XML_H
+
+#include "logger.h"
+
+typedef struct Attribute {
+	const char *attribute;
+	const char *value;
+} Attribute;
+
+
+/*!
+ * Opens XML document.
+ * Creates header and start tag for root element.
+ *
+ * \param rootTag Root tag for the XML document
+ */
+void XMLOpenDocument(const char *rootTag, LogOutputFp log);
+
+/*!
+ * Closes the XML-document.
+ * Creates end tag for root element and closes other open elements
+ * with correct nesting.
+ */
+void XMLCloseDocument();
+
+/*!
+ *  Opens XML-element.
+ *
+ *  \param tag Element to be opened
+ */
+void XMLOpenElement(const char *tag);
+
+/*!
+ * Add attribute to currently open element.
+ *
+ * \param attribute Name of the attribute
+ * \param value Value of the given attribute
+ */
+void XMLAddAttribute(const char *attribute, const char *value);
+
+/*!
+ * Add content to currently open element.
+ *
+ * \param content Content for the currently open element
+ */
+void XMLAddContent(const char *content);
+
+/*!
+ * Closes previously opened element.
+ * Enforces proper nesting by not allowing end elements haphazardly.
+ *
+ * Closes all the opened elements until the given element/tag is found
+ *
+ * \param tag Element to close
+ */
+void XMLCloseElement(const char *tag);
+
+#endif
+
diff --git a/test/test-automation/xml_logger.c b/test/test-automation/xml_logger.c
index e1f04026..8411b066 100644
--- a/test/test-automation/xml_logger.c
+++ b/test/test-automation/xml_logger.c
@@ -23,6 +23,8 @@
 
 #include "logger.h"
 
+#include "xml.h"
+
 #include <SDL/SDL.h>
 
 #include <stdio.h>
@@ -30,315 +32,83 @@
 #include <string.h>
 #include <stdarg.h>
 
-/* \todo
- * - Make XML (and relevant comparisons) case-insensitive
- */
-
-static int xml_enabled = 1;
-
-static int loggingPriority = 0;
-static int nestingDepth = 0;
-
-/*! Definitions of log priorities */
-typedef enum Priority {
-	VERBOSE,
-	DEFAULT,
-} Priority;
-
-/*! Function pointer definitions. \todo Move to logger.h */
-typedef int (*LogOutputFp)(char *);
-
-typedef int (*LogInitFp)(LogOutputFp, Priority);
-typedef int (*LogCleanUptFp)(void);
-
-typedef int (*StartTagFp)(Priority, const char *);
-typedef int (*EndTagFp)(Priority, const char *);
-typedef int (*TagFp)(Priority, const char *, const char *, ...);
-
-
-/*! Function pointer to output function */
-static LogOutputFp OutputFp = NULL;
-
-
-/*! Definitions for tag styles used in Tagify() */
-#define TAG_START 0x00000001
-#define TAG_END   0x00000002
-#define TAG_BOTH  (TAG_START & TAG_END)
-
-/*! Function prototypes \todo move to xml_logger.h */
-int XMLStartTag(Priority priority, const char *tag);
-int XMLEndTag(Priority priority, const char *tag);
-
-int LogGenericOutput(char *message);
-
-
 /*!
- * Defines structure used for "counting" open XML-tags
- */
-typedef struct TagList {
-	const char *tag;
-	struct TagList *next;
-} TagList;
-
-static TagList *openTags = NULL;
-
-/*!
- * Prepend the open tags list
+ * Prints the given message to stderr. Function adds nesting
+ * to the output.
  *
- * \return On error returns non-zero value, otherwise zero will returned
+ * \return Possible error value (\todo)
  */
-static int
-AddOpenTag(const char *tag)
+int
+LogGenericOutput(const char *message)
 {
-	TagList *openTag = SDL_malloc(sizeof(TagList));
-	if(openTag == NULL) {
-		return 1;
+	/*
+	int depth = indentDepth;
+	while(depth--) {
+		fprintf(stderr, " ");
 	}
-	memset(openTag, 0, sizeof(TagList));
-
-	openTag->tag = tag; // Should be fine without malloc?
-	openTag->next = openTags;
-
-	openTags = openTag;
+	*/
 
-	return 0;
+	fprintf(stderr, "%s\n", message);
+	fflush(stderr);
 }
 
-/*!
- * Removes the first tag from the open tag list
- *
- * \return On error returns non-zero value, otherwise zero will returned
- */
-static int
-RemoveOpenTag(const char *tag)
+void
+RunStarted(LogOutputFp outputFn, const char *runnerParameters, time_t eventTime)
 {
-	if(openTags == NULL) {
-		return 1;
-	}
-
-	int retVal = 0;
+	XMLOpenDocument("testlog", outputFn);
 
-	// Tag should always be the same as previously opened tag
-	// It prevents opening and ending tag mismatch
-	if(SDL_strcmp(openTags->tag, tag) == 0) {
-		TagList *openTag = openTags;
-		openTags  = openTags->next;
-
-		free(openTag);
-	} else {
-		printf("Debug | RemoveOpenTag(): open/end tag mismatch");
-		retVal = 1;
-	}
-
-	return retVal;
+	XMLOpenElement("parameters");
+	XMLAddContent(runnerParameters);
+	XMLCloseElement("parameters");
 }
 
-/*!
- * Debug function. Prints the contents of the open tags list.
- */
-static void
-PrintOpenTags()
+void
+RunEnded(time_t endTime, time_t totalRuntime)
 {
-	printf("\nOpen tags:\n");
-
-	TagList *openTag = NULL;
-	for(openTag = openTags; openTag; openTag = openTag->next) {
-		printf("\ttag: %s\n", openTag->tag);
-	}
+	XMLCloseDocument();
 }
 
-/*!
- * Initializes the XML-logger for creating test reports in XML.
- *
- * \return Error code. \todo
- */
-int
-XMLInit(LogOutputFp logOutputFp, Priority priority)
+void
+SuiteStarted(const char *suiteName, time_t eventTime)
 {
-	OutputFp = logOutputFp;
-	loggingPriority = priority;
-
-	//! make "doctype" work with priority level?
-	OutputFp("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
-	XMLStartTag(DEFAULT, "testlog");
-}
+	XMLOpenElement("suite");
 
-/*!
- * Cleans up the logger and closes all open XML-tags
- *
- * \return Error code. \todo
- */
-int
-XMLCleanUp()
-{
-	// Close the open tags
-	TagList *openTag = openTags;
-	while(openTag) {
-		TagList *temp = openTag->next;
-		XMLEndTag(DEFAULT, openTag->tag);
-		openTag = temp;
-	}
+	XMLOpenElement("eventTime");
+	//XMLAddContent(evenTime);
+	XMLCloseElement("eventTime");
 }
 
-/*!
- * Forms a valid XML-tag based on the given parameters
- *
- * \param tag XML-tag to create
- * \param tagStyle Do start or end tag, or both.
- * \param message text content of the tags
- *
- * \return Well-formed XML tag
- */
-static char *
-Tagify(const char *tag, const int tagStyle, const char *message)
+void
+SuiteEnded(int testsPassed, int testsFailed, int testsSkipped,
+           double endTime, time_t totalRuntime)
 {
-	// buffer simplifies the creation of the string
-	const int bufferSize = 1024;
-	char buffer[bufferSize];
-	memset(buffer, 0, bufferSize);
-
-	if(tagStyle & TAG_START) {
-		strcat(buffer, "<");
-		strcat(buffer, tag);
-		strcat(buffer, ">");
-	}
-
-	if(message) {
-		strcat(buffer, message);
-	}
-
-	if(tagStyle & TAG_END) {
-		strcat(buffer, "</");
-		strcat(buffer, tag);
-		strcat(buffer, ">");
-	}
-
-
-	const int size = SDL_strlen(buffer) + 1;
-	char *newTag = SDL_malloc(size * sizeof(char));
-	memset(newTag, 0, size * sizeof(char));
-	memcpy(newTag, buffer, size);
-
-	return newTag;
+	XMLCloseElement("suite");
 }
 
-/*!
- * Creates and outputs an start tag
- *
- * \param priority Priority of the tag
- * \param tag Tag for outputting
- *
- * \return Error code. Non-zero on failure. Zero on success
- */
-int
-XMLStartTag(Priority priority, const char *tag)
+void
+TestStarted(const char *testName, const char *testDescription, time_t startTime)
 {
-	if(priority < loggingPriority) {
-		return 1;
-	}
-
-	AddOpenTag(tag);
-	char *newTag = Tagify(tag, TAG_START, NULL);
-	OutputFp(newTag);
-	SDL_free(newTag);
 
-	nestingDepth++;
 }
 
-/*!
- * Creates and outputs an end tag
- *
- * \param priority Priority of the tag
- * \param tag Tag for outputting
- *
- *  \return Error code. Non-zero on failure. Zero on success
- */
-int
-XMLEndTag(Priority priority, const char *tag)
+void
+TestEnded(const char *testName, const char *testDescription, int testResult,
+          int numAsserts, time_t endTime, time_t totalRuntime)
 {
-	/*
-	Do it before priority check, so incorrect usage of
-	priorities won't mess it up (?)
-	*/
-	nestingDepth--;
-
-	if(priority < loggingPriority) {
-		return 1;
-	}
-
-	RemoveOpenTag(tag);
 
-	char *newTag = Tagify(tag, TAG_END, NULL);
-	OutputFp(newTag);
-	SDL_free(newTag);
 }
 
-/*!
- * Creates an XML-tag including start and end tags and text content
- * between them.
- *
- * \param priority Priority of the tag
- * \param tag Tag for outputting
- * \param fmt Text content of tag as variadic parameter list
- *
- *  \return Error code. Non-zero on failure. Zero on success
- */
-int
-XMLTag(Priority priority, const char *tag, const char *fmt, ...)
+void
+Assert(const char *assertName, int assertResult, const char *assertMessage,
+       time_t eventTime)
 {
-	if(priority < loggingPriority) {
-		return 1;
-	}
-
-	const int bufferSize = 1024;
-	char buffer[bufferSize];
-	memset(buffer, 0, bufferSize);
-
-	va_list list;
-	va_start(list, fmt);
-    vsnprintf(buffer, bufferSize, fmt, list);
-	va_end(list);
 
-	char *newTag = Tagify(tag, TAG_BOTH, buffer);
-	//LogGenericOutput(newTag);
-	OutputFp(newTag);
-	SDL_free(newTag);
 }
 
-/*!
- * Prints the given message to stderr. Function adds nesting
- * to the output.
- *
- * \return Possible error value (\todo)
- */
-int
-LogGenericOutput(char *message)
+void
+Log(const char *logMessage, time_t eventTime)
 {
-	int depth = nestingDepth;
-	while(depth--) {
-		fprintf(stderr, " ");
-	}
-
-	fprintf(stderr, "%s\n", message);
-}
-
 
-
-/*! Quick Dummy functions for testing non-xml output. \todo put to proper place*/
-int DummyInit(LogOutputFp output, Priority priority) {
-	return 0;
-}
-int DummyCleanUp() {
-	return 0;
-}
-int DummyStartTag(Priority priority, const char *tag) {
-	return 0;
-}
-int DummyEndTag(Priority priority, const char *tag) {
-	return 0;
-}
-int DummyTag(Priority priority, const char *tag, const char *fmt, ...) {
-	return 0;
 }
 
 
@@ -348,50 +118,10 @@ int DummyTag(Priority priority, const char *tag, const char *fmt, ...) {
 int
 main(int argc, char *argv[])
 {
-	LogInitFp LogInit = NULL;
-	LogCleanUptFp LogCleanUp = NULL;
-	StartTagFp StartTag = NULL;
-	EndTagFp EndTag = NULL;
-	TagFp Tag = NULL;
-
-	if(xml_enabled) {
-		// set logger functions to XML
-		LogInit = XMLInit;
-		LogCleanUp = XMLCleanUp;
-
-		StartTag = XMLStartTag;
-		EndTag = XMLEndTag;
-		Tag = XMLTag;
-	} else {
-		// When no XML-output is desired, dummy functions are used
-		LogInit = DummyInit;
-		LogCleanUp = DummyCleanUp;
-
-		StartTag = DummyStartTag;
-		EndTag = DummyEndTag;
-		Tag = DummyTag;
-	}
-
-	LogInit(LogGenericOutput, VERBOSE);
-
-	StartTag(DEFAULT, "hello");
-	StartTag(DEFAULT, "world");
-	EndTag(DEFAULT, "world");
-	//EndTag(DEFAULT, "hello");
-
-	LogCleanUp();
-
-#if 0
-	XMLStartTag("log");
-	XMLStartTag("suite");
-	XMLStartTag("test");
-
-	XMLEndTag("test");
-	XMLEndTag("suite");
-
-
-	PrintOpenTags();
-#endif
+	RunStarted(LogGenericOutput, "All the data from harness", 0);
+	SuiteStarted("Suite data here", 0);
+	SuiteEnded(0, 0, 0, 0.0f, 0);
+	RunEnded(0, 0);
 
 	return 0;
 }
-- 
2.18.1