OSDN Git Service

new
authortsntsumi <tsntsumi@users.sourceforge.jp>
Thu, 11 Sep 2003 01:48:49 +0000 (01:48 +0000)
committertsntsumi <tsntsumi@users.sourceforge.jp>
Thu, 11 Sep 2003 01:48:49 +0000 (01:48 +0000)
src/ccunit/CCUnitReadSuite.c [new file with mode: 0644]

diff --git a/src/ccunit/CCUnitReadSuite.c b/src/ccunit/CCUnitReadSuite.c
new file mode 100644 (file)
index 0000000..c651285
--- /dev/null
@@ -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 <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));
+}