From 03e93bc7c4d2270cedb1d53c5032ea108dd4f3ec Mon Sep 17 00:00:00 2001 From: tsntsumi Date: Thu, 11 Sep 2003 01:48:49 +0000 Subject: [PATCH] new --- src/ccunit/CCUnitReadSuite.c | 629 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 629 insertions(+) create mode 100644 src/ccunit/CCUnitReadSuite.c diff --git a/src/ccunit/CCUnitReadSuite.c b/src/ccunit/CCUnitReadSuite.c new file mode 100644 index 0000000..c651285 --- /dev/null +++ b/src/ccunit/CCUnitReadSuite.c @@ -0,0 +1,629 @@ +/* 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 +#include +#include +#include +#include + +/** + * 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 line. + * + * @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)); +} -- 2.11.0