+++ /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>
-
-/**
- * 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;
-}
-
-/**
- * destroy test def.
- *
- * @param test testdef to destroy.
- */
-static void destroyTestDef (CCUnitTestDef* test)
-{
- if (!test)
- return;
- safe_free (test->name);
-}
-
-static void destroyTestSuiteDef (CCUnitTestSuiteDef* suite);
-static void destroyTestCaseDef (CCUnitTestCaseDef* testCase);
-
-/**
- * delete test def.
- *
- * @param test testdef to delete.
- */
-static void deleteTestDef (CCUnitTestDef* test)
-{
- if (!test)
- return;
- if (test->type == ccunitTypeSuite)
- destroyTestSuiteDef ((CCUnitTestSuiteDef*)test);
- else if (test->type == ccunitTypeCase)
- destroyTestCaseDef ((CCUnitTestCaseDef*)test);
- destroyTestDef (test);
- safe_free (test);
-}
-
-/*
- * create new test suite def.
- */
-CCUnitTestSuiteDef* ccunit_newTestSuiteDef (const char* name)
-{
- CCUnitTestSuiteDef* suite = calloc (1, sizeof (*suite));
- if (!suite)
- return suite;
- initTestDef (&suite->super, ccunitTypeSuite, name);
- ccunit_initList (&suite->testdefs);
- return suite;
-}
-
-/**
- * destroy test suite def.
- *
- * @param suite test suite def.
- */
-static void destroyTestSuiteDef (CCUnitTestSuiteDef* suite)
-{
- ccunit_deleteList (&suite->testdefs, (void(*)(void*))deleteTestDef);
-}
-
-/*
- * delete test suite def.
- */
-void ccunit_deleteTestSuiteDef (CCUnitTestSuiteDef* suite)
-{
- if (!suite)
- return;
- deleteTestDef (&suite->super);
-}
-
-/**
- * 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 function.
- *
- * @param type return type of function.
- * @param name function name.
- * @param desc description.
- * @return new test function def.
- */
-static CCUnitTestFuncDef* newTestFuncDef (const char* type,
- const char* name,
- const char* desc)
-{
- struct CCUnitTestFuncDef* 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 = safe_strdup (desc);
- return f;
-}
-
-/**
- * delete test func def.
- * @param func test func def to delete.
- */
-static void deleteTestFuncDef (CCUnitTestFuncDef* func)
-{
- if (!func)
- return;
- safe_free (func->type);
- safe_free (func->name);
- safe_free (func->desc);
- free (func);
-}
-
-/**
- * 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->super, ccunitTypeCase, name);
- ccunit_initList (&testCase->testFuncs);
- return testCase;
-}
-
-/**
- * copy test function def.
- *
- * @param def function def.
- * @return new test function def.
- */
-static inline CCUnitTestFuncDef* copyTestFuncDef (CCUnitTestFuncDef* def)
-{
- if (!def)
- return NULL;
- return newTestFuncDef (def->type, def->name, def->desc);
-}
-
-/**
- * destroy test case def.
- * @param testCase test case def to destroy.
- */
-static void destroyTestCaseDef (CCUnitTestCaseDef* testCase)
-{
- ccunit_deleteList (&testCase->testFuncs, (void(*)(void*))deleteTestFuncDef);
- deleteTestFuncDef (testCase->setUp);
- deleteTestFuncDef (testCase->tearDown);
-}
-
-/**
- * delete test case def.
- * @param testCase test case def to delete.
- */
-static inline void deleteTestCaseDef (CCUnitTestCaseDef* testCase)
- __attribute__ ((unused));
-static void deleteTestCaseDef (CCUnitTestCaseDef* testCase)
-{
- deleteTestDef (&testCase->super);
-}
-
-/**
- * Read line.
- */
-struct Line
-{
- char* str; /**< read line buffer */
- size_t length; /**< line length */
- size_t capacity; /**< buffer capacity */
- size_t lno; /**< line number */
- FILE* ifp; /**< input stream */
- const char* fname; /**< input file name */
-};
-
-typedef struct Line Line;
-
-/**
- * Current processing line.
- */
-static Line line;
-
-/**
- * Get one line from stream.
- * This function 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:%" PFMTSIZE_T ": \"%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 ()
-{
- if (strcmp (line.str, "/**") != 0) /* not a comment */
- ;
- else if (line.str[3] == '*' || line.str[3] == '/') /* not doc */
- ;
- else
- {
- ccunit_dbg ("found doc comment:%" PFMTSIZE_T ": %s", line.lno, line.str);
- return readDocCommentContents ();
- }
- return NULL;
-}
-
-/**
- * get test suite def.
- *
- * @param str comment string.
- * @return test suite name.
- */
-static char* getSuiteName (const char* str)
-{
- const char* name = NULL;
- static const char* const prefix = "TEST SUITE:";
- const size_t prefixLen = strlen (prefix);
- if (strncasecmp (str, prefix, prefixLen) == 0)
- {
- for (name = str + prefixLen; *name; name ++)
- if (!isspace (*name))
- break;
- if (!*name)
- {
- name = NULL;
- ccunit_err ("no suite name: %s. near line %" PFMTSIZE_T "",
- str, line.lno);
- }
- }
- else
- ccunit_dbg ("not a suite name: %s", str);
- return (char*)name;
-}
-
-
-static char* getEndOfSuite (const char* str)
-{
- static const char* const prefix = "END TEST SUITE";
- 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 suite: %s", str);
- }
- else
- {
- for (; *name; name ++)
- if (!isspace (*name))
- break;
- if (!*name)
- ;
- else
- ccunit_log ("end test suite: %s", name);
- }
- }
- else
- ccunit_dbg ("not a end suite: %s", str);
- return (char*)name;
-}
-
-/**
- * get test case def.
- *
- * @param str comment string.
- * @return group attribute object.
- */
-static char* getCaseName (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 %" PFMTSIZE_T "",
- str, line.lno);
- }
- }
- else
- ccunit_dbg ("not a test case name: %s", str);
- return (char*)name;
-}
-
-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 funcdef.
- *
- * @param type required type string.
- * @param prefix required function name prefix.
- * @param desc description.
- * @return funcdef object.
- */
-static CCUnitTestFuncDef* readFunc (const char* type,
- const char* prefix,
- const char* desc)
-{
- char* typ;
- char* name;
- ccunit_dbg ("read func: %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 newTestFuncDef (type, name, desc);
- }
- else
- ccunit_dbg ("name mismatch: %s %s", prefix, name);
- return NULL;
-}
-
-/**
- * read test case function.
- *
- * @param parent parent suite.
- * @param caseName test case name.
- */
-static void readTestCase (CCUnitTestSuiteDef* parent, const char* caseName)
-{
- CCUnitTestSuiteDef* suite = newTestSuiteDef (caseName);
- CCUnitTestCaseDef* testCase = NULL;
- CCUnitTestFuncDef* f = NULL;
- CCUnitTestFuncDef* setUp = NULL;
- CCUnitTestFuncDef* tearDown = NULL;
- 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 = getCaseName (doc)) != NULL)
- {
- ccunit_err ("%s:%lu: unbaranced end case comment '%s', "
- "need /** end case: %s */",
- line.fname, line.lno, doc, caseName);
- readTestCase (suite, name);
- safe_free (doc);
- }
- else if ((name = getEndOfCase (doc)) != NULL)
- {
- safe_free (doc);
- break;
- }
- desc = doc;
- }
- /* setUp function def */
- else if ((f = readTestFuncDef ("void", "setUp", desc)) != NULL)
- {
- if (!testCase)
- setUp = f;
- else if (!testCase->setUp)
- testCase->setUp = f;
- else
- {
- ccunit_err ("%s:%lu: multiply setUp function defined. %s\n"
- " perhaps missing /** test case: ... */",
- line.fname, line.lno, line.str);
- deleteTestFuncDef (f);
- f = NULL;
- }
- safe_free (desc);
- }
- /* tearDown function def */
- else if ((f = readTestFuncDef ("void", "tearDown", desc)) != NULL)
- {
- if (!testCase)
- tearDown = f;
- else if (!testCase->tearDown)
- testCase->tearDown = f;
- else
- {
- ccunit_err ("%s:%lu: multiply tearDown function defined. %s\n"
- " perhaps missing /** test case: ... */",
- line.fname, line.lno, line.str);
- testCase = newTestCaseDef (NULL);
- addTestDef (suite, &testCase->super);
- tearDown = f;
- }
- safe_free (desc);
- }
- /* if test case function def, then read as test case. */
- else if ((f = readTestFuncDef ("void", "test", desc)) != NULL)
- {
- if (testCase != NULL && !testCase->runTest)
- {
- testCase->runTest = f;
- if (!testCase->super.name)
- testCase->super.name = safe_strdup (f->name);
- if (!testCase->desc)
- testCase->desc = safe_strdup (f->desc);
- }
- else
- {
- testCase = newTestCaseDef (desc);
- testCase->runTest = f;
- testCase->setUp = setUp;
- testCase->tearDown = tearDown;
- addTestDef (suite, &testCase->super);
- }
- safe_free (desc);
- }
- else
- ;
- }
- safe_free (desc);
-}
-
-/**
- * read test suite def.
- *
- * @param sname parent suitedef name.
- */
-static CCUnitTestSuiteDef* readSuite (const char* sname)
-{
- CCUnitTestSuiteDef* suite = ccunit_newTestSuiteDef (sname);
- CCUnitTestFuncDef* 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 = getCaseName (doc)) != NULL)
- {
- readTestCase (suite, 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);
- }
- desc = doc;
- }
- else if ((f = readFunc ("void", "test", desc)) != NULL
- || (f = readFunc ("void", "setUp", desc)) != NULL
- || (f = readFunc ("void", "tearDown", desc)) != NULL)
- {
- ccunit_err ("%s:%lu: missing test case start comment '%s': ignored",
- line.fname, line.lno, line.str);
- safe_free (desc);
- }
- else
- ;
- }
- safe_free (desc);
-}
-
-/*
- * read test unit suite from specified stream.
- * @param sname new suite name.
- * @param fname file name to read.
- * @return new test suite def.
- */
-CCUniteTestSuiteDef* ccunit_readSuite (const char* sname, const char* fname)
-{
- CCUnitTestSuiteDef* suite;
- 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 NULL;
- }
- line.fname = fname;
- }
- suite = readSuite (!sname ? "suite" : sname);
- safe_free (line.str);
- safe_free (desc);
- if (line.ifp != stdin)
- fclose (line.ifp);
- memset (&line, 0, sizeof (line));
- return suite;
-}