--- /dev/null
+/* Copyright (C) 2003 TSUTSUMI Kikuo.
+ This file is part of the CCUnit Library.
+
+ The CCUnit Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public License
+ as published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ The CCUnit Library is distributed in the hope that it will be
+ useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the CCUnit Library; see the file COPYING.LESSER.
+ If not, write to the Free Software Foundation, Inc., 59 Temple
+ Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+/*
+ * $Id$
+ */
+
+#include <ccunit/CCUnitMakeSuite.h>
+#include <ccunit/CCUnitLogMessage.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+
+/**
+ * destroy test def.
+ *
+ * @param test testdef to destroy.
+ */
+static void destroyTestDef (CCUnitTestDef* test)
+{
+ if (!test)
+ return;
+ safe_free (test->name);
+}
+
+/**
+ * init test def.
+ *
+ * @param test testdef.
+ * @param type test type.
+ * @param name test name.
+ */
+static CCUnitTestDef* initTestDef (CCUnitTestDef* test,
+ CCUnitTestType_t type,
+ const char* name)
+{
+ test->type = type;
+ test->name = safe_strdup (name);
+ return test;
+}
+
+/**
+ * delete test def.
+ *
+ * @param test testdef to delete.
+ */
+static void deleteTestDef (CCUnitTestDef* test)
+{
+ if (!test)
+ return;
+ if (!test->dtor)
+ ;
+ else
+ test->dtor (test);
+ safe_free (test);
+}
+
+/**
+ * destroy test suite def.
+ *
+ * @param suite test suite def.
+ */
+static void destroyTestSuiteDef (CCUnitTestSuiteDef* suite)
+{
+ ccunit_deleteList (&suite->testdefs, (void(*)(void*))deleteTestDef);
+ destroyTestDef (&suite->testdef);
+}
+
+CCUnitTestSuiteDef* ccunit_newTestSuiteDef (const char* name)
+{
+ CCUnitTestSuiteDef* suite = calloc (1, sizeof (*suite));
+ if (!suite)
+ return suite;
+ initTestDef (&suite->testdef, ccunitTypeSuite, name);
+ suite->testdef.dtor = (void(*)(CCUnitTestDef*))destroyTestSuiteDef;
+ ccunit_initList (&suite->testdefs);
+ return suite;
+}
+
+/**
+ * add test to test suite.
+ *
+ * @param suite test suite to add.
+ * @param test test group.
+ * @return added test.
+ */
+static CCUnitTestDef* addTestDef (CCUnitTestSuiteDef* suite,
+ CCUnitTestDef* test)
+{
+ if (!suite || !test)
+ return NULL;
+ ccunit_log ("add test: %s", test->name);
+ ccunit_addList (&suite->testdefs, test);
+ return test;
+}
+
+/**
+ * create new test fixture.
+ *
+ * @param type return type of fixture.
+ * @param name fixture name.
+ * @param desc description.
+ * @return new test fixture def.
+ */
+static CCUnitTestFixtureDef* newTestFixtureDef (const char* type,
+ const char* name,
+ const char* desc)
+{
+ struct CCUnitTestFixtureDef* f = calloc (1, sizeof (*f));
+ ccunit_log ("create new test func: %s %s", type, name);
+ if (!f)
+ return f;
+ f->type = safe_strdup (type);
+ f->name = safe_strdup (name);
+ f->desc = !desc ? safe_strdup (name) : strdup (desc);
+ return f;
+}
+
+/**
+ * delete test func def.
+ * @param func test func def to delete.
+ */
+static void deleteTestFixtureDef (CCUnitTestFixtureDef* func)
+{
+ if (!func)
+ return;
+ safe_free (func->type);
+ safe_free (func->name);
+ safe_free (func->desc);
+ free (func);
+}
+
+/**
+ * destroy test case def.
+ * @param testCase test case def to destroy.
+ */
+static void destroyTestCaseDef (CCUnitTestCaseDef* testCase)
+{
+ ccunit_deleteList (&testCase->fixtures, (void(*)(void*))deleteTestFixtureDef);
+ deleteTestFixtureDef (testCase->setUp);
+ deleteTestFixtureDef (testCase->tearDown);
+ destroyTestDef (&testCase->testdef);
+}
+
+/**
+ * create new test case def.
+ *
+ * @param name test case name.
+ * @param setUp test case setup func def.
+ * @param tearDown test case tearDown func def.
+ */
+static CCUnitTestCaseDef* newTestCaseDef (const char* name)
+{
+ CCUnitTestCaseDef* testCase = calloc (1, sizeof (*testCase));
+ ccunit_log ("create new test case: %s", name);
+ if (!testCase)
+ return NULL;
+ initTestDef (&testCase->testdef, ccunitTypeCase, name);
+ testCase->testdef.dtor = (void(*)(CCUnitTestDef*))destroyTestCaseDef;
+ ccunit_initList (&testCase->fixtures);
+ return testCase;
+}
+
+/**
+ * Read line.
+ */
+struct _CCUnitLine
+{
+ char* str; /**< read line buffer */
+ size_t length; /**< line length */
+ size_t capacity; /**< buffer capacity */
+ unsigned long lno; /**< line number */
+ FILE* ifp; /**< input stream */
+ const char* fname; /**< input file name */
+};
+
+/**
+ * Current processing line.
+ */
+static struct _CCUnitLine line;
+
+/**
+ * Get one line from stream.
+ * This fixture copies a read line on the global variable <code>line</code>.
+ *
+ * @return When reading succeeds, value except for the zero is
+ * returned. When an error occurs, a zero is returned.
+ */
+static int readline ()
+{
+ static const size_t MIN_LINE_BUF_LEN = 512;
+ /* buffer hasn't been allocate yet */
+ if (line.str == NULL)
+ {
+ line.capacity = MIN_LINE_BUF_LEN;
+ line.str = calloc (line.capacity, sizeof(line.str[0]));
+ line.length = 0;
+ line.lno = 0;
+ }
+ /* shrink to minimum size */
+ else if (line.capacity > MIN_LINE_BUF_LEN)
+ {
+ line.capacity = MIN_LINE_BUF_LEN;
+ char* str = realloc (line.str, line.capacity);
+ if (str)
+ line.str = str;
+ }
+ char* insertAt = line.str;
+ size_t restSize = line.capacity;
+ line.length = 0;
+ char* sp = 0;
+ while ((sp = fgets (insertAt, restSize, line.ifp)) != NULL)
+ {
+ line.length += strlen(insertAt);
+ /* read complete whole line */
+ if (line.str[line.length-1] == '\n'
+ || line.str[line.length-1] == '\r') /* for mac? */
+ {
+ break;
+ }
+ else
+ {
+ /* to extend capacity for rest of line */
+ size_t newCapacity = line.capacity * 2 / 3;
+ char* str = realloc (line.str, newCapacity);
+ if (!str)
+ {
+ ccunit_log ("/* no more memory */");
+ break;
+ }
+ line.str = str;
+ restSize = newCapacity - line.capacity;
+ insertAt = str + line.capacity;
+ line.capacity = newCapacity;
+ }
+ }
+ if (!sp)
+ return 0;
+ /* chomp CR/LF */
+ char* tail = line.str + line.length - 1;
+ while (*tail == '\n' || *tail == '\r') /* for dos/mac? */
+ {
+ line.length --;
+ *(tail --) = '\0';
+ }
+ line.lno ++;
+ return 1;
+}
+
+/**
+ * read contents of doc comment.
+ *
+ * @return comment string. or NULL when error occurred.
+ */
+static char* readDocCommentContents ()
+{
+ bool eoc = false; /* reach end of comment */
+ char* content = NULL; /* comment content */
+ size_t length = 0; /* content length */
+ char* start = NULL; /* start of content */
+ char* end = NULL; /* end of content */
+ ccunit_log ("readDocCommentContent");
+ start = line.str + 2;
+ while (!eoc)
+ {
+ ccunit_dbg ("read from:%lu: \"%s\"", line.lno, start);
+ /* skip white spaces */
+ for (; *start && isspace (*start); start ++)
+ ;
+ if (*start != '*') /* block comment '*' */
+ ;
+ else if (start[1] == '/') /* eoc */
+ ;
+ else /* skip white spaces */
+ for (start ++; *start && isspace (*start); start ++)
+ ;
+ /* seek to eol or end of comment */
+ for (end = start; *end; end ++)
+ if (end[0] == '*' && end[1] == '/')
+ {
+ ccunit_log ("end of comment");
+ eoc = true;
+ break;
+ }
+ /* trim trailing white space */
+ for (end --; start < end; end --)
+ if (!isspace (*end))
+ {
+ end ++;
+ break;
+ }
+ /* did a comment exist? */
+ if (start < end)
+ {
+ int len = (int)(end - start);
+ char* newContent = realloc (content, length + len + 2);
+ if (!newContent)
+ {
+ ccunit_err ("no more memory");
+ break;
+ }
+ if (length > 0)
+ newContent[length ++] = ' '; /* word space */
+ memcpy (newContent + length, start, len);
+ length += len;
+ newContent[length] = '\0';
+ content = newContent;
+ ccunit_log ("get: \"%*.*s\"", len, len, start);
+ }
+ if (eoc || !readline ())
+ break;
+ start = line.str;
+ }
+ ccunit_dbg ("comment content: \"%s\"", content);
+ return content;
+}
+
+/**
+ * read document comment.
+ *
+ * @return comment content if matched, or NULL if not matched.
+ */
+static char* readDocComment ()
+{
+ const char* cmnt = "/**";
+ if (strncmp (line.str, cmnt, strlen(cmnt)) != 0) /* not a comment */
+ ;
+ else if (line.str[3] == '*' || line.str[3] == '/') /* not doc */
+ ;
+ else
+ {
+ ccunit_dbg ("found doc comment:%lu: %s", line.lno, line.str);
+ return readDocCommentContents ();
+ }
+ return NULL;
+}
+
+/**
+ * get test case def.
+ *
+ * @param str comment string.
+ * @return group attribute object.
+ */
+static char* getTestCaseName (const char* str)
+{
+ static const char* const prefix = "TEST CASE:";
+ const size_t prefixLen = strlen (prefix);
+ const char* name = NULL;
+ if (strncasecmp (str, prefix, prefixLen) == 0)
+ {
+ for (name = str + prefixLen; *name; name ++)
+ if (!isspace (*name))
+ break;
+ if (!*name)
+ {
+ name = NULL;
+ ccunit_err ("no test case name: %s. near line %lu",
+ str, line.lno);
+ }
+ }
+ else
+ ccunit_dbg ("not a test case name: %s", str);
+ return (char*)name;
+}
+
+/**
+ * get end of case string.
+ * @param str string.
+ * @return eoc string.
+ */
+static char* getEndOfCase (const char* str)
+{
+ static const char* const prefix = "END TEST CASE";
+ const size_t prefixLen = strlen (prefix);
+ const char* name = NULL;
+ if (strncasecmp (str, prefix, prefixLen) == 0)
+ {
+ name = str + prefixLen;
+ if (*name && !isspace (*name) && !ispunct (*name))
+ {
+ name = NULL;
+ ccunit_dbg ("not a end of test case: %s", str);
+ }
+ else
+ {
+ for (; *name; name ++)
+ if (!isspace (*name))
+ break;
+ if (!*name)
+ ;
+ else
+ ccunit_log ("end of test case: %s", name);
+ }
+ }
+ else
+ ccunit_dbg ("not a end of test case: %s", str);
+ return (char*)name;
+}
+
+/**
+ * read test fixturedef.
+ *
+ * @param type required type string.
+ * @param prefix required fixture name prefix.
+ * @param desc description.
+ * @return funcdef object.
+ */
+static CCUnitTestFixtureDef* readFixture (const char* type,
+ const char* prefix,
+ const char* desc)
+{
+ char* typ;
+ char* name;
+ ccunit_dbg ("read fixture: %s %s... from '%s'", type, prefix, line.str);
+ for (typ = line.str; *typ; typ ++)
+ if (!isspace (*typ))
+ break;
+ if (strncmp (typ, type, strlen (type)) != 0)
+ {
+ ccunit_dbg ("type mismatch: %s %s", type, typ);
+ return NULL;
+ }
+ name = typ + strlen (type);
+ if (*name && !isspace (*name))
+ {
+ ccunit_dbg ("type mismatch: %s %s", type, name);
+ return NULL;
+ }
+ for (;;)
+ {
+ for (; *name; name ++)
+ if (!isspace (*name))
+ break;
+ if (*name)
+ break;
+ if (!readline ())
+ {
+ ccunit_err ("unexpected EOF");
+ return NULL;
+ }
+ }
+ if (strncmp (name, prefix, strlen(prefix)) == 0)
+ {
+ char* tail;
+ for (tail = name + 1; *tail; tail ++)
+ if (isspace (*tail) || *tail == '(')
+ {
+ *tail = '\0';
+ break;
+ }
+ return newTestFixtureDef (type, name, desc);
+ }
+ else
+ ccunit_dbg ("name mismatch: %s %s", prefix, name);
+ return NULL;
+}
+
+/**
+ * read test case function.
+ *
+ * @param parent parent suite.
+ * @param cname test case name to read.
+ */
+static void readTestCase (CCUnitTestSuiteDef* parent, const char* cname)
+{
+ CCUnitTestSuiteDef* suite;
+ CCUnitTestCaseDef* testCase;
+ CCUnitTestFixtureDef* f = NULL;
+ char* name;
+ char* doc;
+ char* desc = NULL;
+ suite = ccunit_newTestSuiteDef (NULL);
+ if (!suite)
+ return;
+ testCase = newTestCaseDef (cname);
+ if (!testCase)
+ {
+ deleteTestDef (&suite->testdef);
+ return;
+ }
+ addTestDef (parent, &suite->testdef);
+ addTestDef (suite, &testCase->testdef);
+ while (readline ())
+ {
+ /* setUp function def */
+ if ((f = readFixture ("void", "setUp", desc)) != NULL)
+ {
+ if (!testCase->setUp)
+ testCase->setUp = f;
+ else
+ {
+ ccunit_err ("%s:%lu: setUp multiply defined %s... ignored.\n"
+ " previous definition is %s\n"
+ " perhaps missing /** test case: ... */",
+ line.fname, line.lno, f->name, testCase->setUp->name);
+ deleteTestFixtureDef (f);
+ }
+ safe_free (desc);
+ }
+ /* tearDown function def */
+ else if ((f = readFixture ("void", "tearDown", desc)) != NULL)
+ {
+ if (!testCase->tearDown)
+ testCase->tearDown = f;
+ else
+ {
+ ccunit_err ("%s:%lu: tearDown multiply defined %s... ignored.\n"
+ " previous definition is %s\n"
+ " perhaps missing /** test case: ... */",
+ line.fname, line.lno, f->name, testCase->tearDown->name);
+ deleteTestFixtureDef (f);
+ }
+ safe_free (desc);
+ }
+ /* if test fixture function def, then read as test fixture. */
+ else if ((f = readFixture ("void", "test", desc)) != NULL)
+ {
+ ccunit_addList (&testCase->fixtures, f);
+ safe_free (desc);
+ }
+ /* if current line is javaDoc comment, then read as description. */
+ else if ((doc = readDocComment ()) != NULL)
+ {
+ if ((name = getTestCaseName (doc)) != NULL)
+ {
+ ccunit_err ("%s:%lu: unbaranced end case comment '%s', "
+ "need /** end case: %s */",
+ line.fname, line.lno, doc, cname);
+ readTestCase (suite, name);
+ safe_free (doc);
+ }
+ else if ((name = getEndOfCase (doc)) != NULL)
+ {
+ ccunit_log ("exit test case: %s", testCase->testdef.name);
+ safe_free (doc);
+ break;
+ }
+ desc = doc;
+ }
+ else
+ ;
+ }
+ safe_free (desc);
+}
+
+/**
+ * read test suite def.
+ *
+ * @param parent parent suitedef.
+ */
+static void readSuite (CCUnitTestSuiteDef* parent)
+{
+ CCUnitTestFixtureDef* f;
+ const char* name;
+ char* doc;
+ char* desc = NULL;
+ while (readline ())
+ {
+ /* if current line is javaDoc comment, then read as description. */
+ if ((doc = readDocComment ()) != NULL)
+ {
+ if ((name = getTestCaseName (doc)) != NULL)
+ {
+ readTestCase (parent, name);
+ safe_free (doc);
+ }
+ else if ((name = getEndOfCase (doc)) != NULL)
+ {
+ ccunit_err ("%s:%lu: invalid end test case comment '%s'",
+ line.fname, line.lno, doc);
+ safe_free (doc);
+ }
+ else
+ desc = doc;
+ }
+ else if ((f = readFixture ("void", "test", desc)) != NULL
+ || (f = readFixture ("void", "setUp", desc)) != NULL
+ || (f = readFixture ("void", "tearDown", desc)) != NULL)
+ {
+ ccunit_err ("%s:%lu: missing test case start comment '%s': ignored",
+ line.fname, line.lno, line.str);
+ deleteTestFixtureDef (f);
+ safe_free (desc);
+ }
+ else
+ ;
+ }
+ safe_free (desc);
+}
+
+void ccunit_readSuite (const char* fname, CCUnitTestSuiteDef* parent)
+{
+ if (strcmp (fname, "-") == 0) /* special file name '-' as stdin */
+ {
+ line.ifp = stdin;
+ line.fname = "stdin";
+ }
+ else
+ {
+ line.ifp = fopen (fname, "r");
+ if (!line.ifp) /* open error */
+ {
+ ccunit_err ("can't open file '%s': %s. skipped.\n",
+ fname, strerror (errno));
+ return;
+ }
+ line.fname = fname;
+ }
+ readSuite (parent);
+ safe_free (line.str);
+ if (line.ifp != NULL && line.ifp != stdin)
+ fclose (line.ifp);
+ memset (&line, 0, sizeof (line));
+}