OSDN Git Service

new
[ccunit/ccunit.git] / src / ccunit / CCUnitReadSuite.c
1 /* Copyright (C) 2003 TSUTSUMI Kikuo.
2    This file is part of the CCUnit Library.
3
4    The CCUnit Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public License
6    as published by the Free Software Foundation; either version 2.1 of
7    the License, or (at your option) any later version.
8
9    The CCUnit Library is distributed in the hope that it will be
10    useful, but WITHOUT ANY WARRANTY; without even the implied warranty
11    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU Lesser General Public License for more details.
13
14    You should have received a copy of the GNU Lesser General Public
15    License along with the CCUnit Library; see the file COPYING.LESSER.
16    If not, write to the Free Software Foundation, Inc., 59 Temple
17    Place - Suite 330, Boston, MA 02111-1307, USA.  
18 */
19 /*
20  * $Id$
21  */
22
23 #include <ccunit/CCUnitMakeSuite.h>
24 #include <ccunit/CCUnitLogMessage.h>
25 #include <stdio.h>
26 #include <ctype.h>
27 #include <errno.h>
28
29 /**
30  * destroy test def.
31  *
32  * @param test testdef to destroy.
33  */
34 static void destroyTestDef (CCUnitTestDef* test)
35 {
36   if (!test)
37     return;
38   safe_free (test->name);
39 }
40
41 /**
42  * init test def.
43  *
44  * @param test testdef.
45  * @param type test type.
46  * @param name test name.
47  */
48 static CCUnitTestDef* initTestDef (CCUnitTestDef* test,
49                                    CCUnitTestType_t type,
50                                    const char* name)
51 {
52   test->type = type;
53   test->name = safe_strdup (name);
54   return test;
55 }
56
57 /**
58  * delete test def.
59  *
60  * @param test testdef to delete.
61  */
62 static void deleteTestDef (CCUnitTestDef* test)
63 {
64   if (!test)
65     return;
66   if (!test->dtor)
67     ;
68   else
69     test->dtor (test);
70   safe_free (test);
71 }
72
73 /**
74  * destroy test suite def.
75  *
76  * @param suite test suite def.
77  */
78 static void destroyTestSuiteDef (CCUnitTestSuiteDef* suite)
79 {
80   ccunit_deleteList (&suite->testdefs, (void(*)(void*))deleteTestDef);
81   destroyTestDef (&suite->testdef);
82 }
83
84 CCUnitTestSuiteDef* ccunit_newTestSuiteDef (const char* name)
85 {
86   CCUnitTestSuiteDef* suite = calloc (1, sizeof (*suite));
87   if (!suite)
88     return suite;
89   initTestDef (&suite->testdef, ccunitTypeSuite, name);
90   suite->testdef.dtor = (void(*)(CCUnitTestDef*))destroyTestSuiteDef;
91   ccunit_initList (&suite->testdefs);
92   return suite;
93 }
94
95 /**
96  * add test to test suite.
97  *
98  * @param suite test suite to add.
99  * @param test test group.
100  * @return added test.
101  */
102 static CCUnitTestDef* addTestDef (CCUnitTestSuiteDef* suite,
103                                   CCUnitTestDef* test)
104 {
105   if (!suite || !test)
106     return NULL;
107   ccunit_log ("add test: %s", test->name);
108   ccunit_addList (&suite->testdefs, test);
109   return test;
110 }
111
112 /**
113  * create new test fixture.
114  *
115  * @param type return type of fixture.
116  * @param name fixture name.
117  * @param desc description.
118  * @return new test fixture def.
119  */
120 static CCUnitTestFixtureDef* newTestFixtureDef (const char* type,
121                                                 const char* name,
122                                                 const char* desc)
123 {
124   struct CCUnitTestFixtureDef* f = calloc (1, sizeof (*f));
125   ccunit_log ("create new test func: %s %s", type, name);
126   if (!f)
127     return f;
128   f->type = safe_strdup (type);
129   f->name = safe_strdup (name);
130   f->desc = !desc ? safe_strdup (name) : strdup (desc);
131   return f;
132 }
133
134 /**
135  * delete test func def.
136  * @param func test func def to delete.
137  */
138 static void deleteTestFixtureDef (CCUnitTestFixtureDef* func)
139 {
140   if (!func)
141     return;
142   safe_free (func->type);
143   safe_free (func->name);
144   safe_free (func->desc);
145   free (func);
146 }
147
148 /**
149  * destroy test case def.
150  * @param testCase test case def to destroy.
151  */
152 static void destroyTestCaseDef (CCUnitTestCaseDef* testCase)
153 {
154   ccunit_deleteList (&testCase->fixtures, (void(*)(void*))deleteTestFixtureDef);
155   deleteTestFixtureDef (testCase->setUp);
156   deleteTestFixtureDef (testCase->tearDown);
157   destroyTestDef (&testCase->testdef);
158 }
159
160 /**
161  * create new test case def.
162  *
163  * @param name test case name.
164  * @param setUp test case setup func def.
165  * @param tearDown test case tearDown func def.
166  */
167 static CCUnitTestCaseDef* newTestCaseDef (const char* name)
168 {
169   CCUnitTestCaseDef* testCase = calloc (1, sizeof (*testCase));
170   ccunit_log ("create new test case: %s", name);
171   if (!testCase)
172     return NULL;
173   initTestDef (&testCase->testdef, ccunitTypeCase, name);
174   testCase->testdef.dtor = (void(*)(CCUnitTestDef*))destroyTestCaseDef;
175   ccunit_initList (&testCase->fixtures);
176   return testCase;
177 }
178
179 /**
180  * Read line.
181  */
182 struct _CCUnitLine
183 {
184   char* str;                                    /**< read line buffer */
185   size_t length;                                /**< line length */
186   size_t capacity;                              /**< buffer capacity */
187   unsigned long lno;                            /**< line number */
188   FILE* ifp;                                    /**< input stream */
189   const char* fname;                            /**< input file name */
190 };
191
192 /**
193  * Current processing line.
194  */
195 static struct _CCUnitLine line;
196
197 /**
198  * Get one line from stream.
199  * This fixture copies a read line on the global variable <code>line</code>.
200  *
201  * @return When reading succeeds, value except for the zero is
202  * returned. When an error occurs, a zero is returned.
203  */
204 static int readline ()
205 {
206   static const size_t MIN_LINE_BUF_LEN = 512;
207   /* buffer hasn't been allocate yet */
208   if (line.str == NULL)
209     {
210       line.capacity = MIN_LINE_BUF_LEN;
211       line.str = calloc (line.capacity, sizeof(line.str[0]));
212       line.length = 0;
213       line.lno = 0;
214     }
215   /* shrink to minimum size */
216   else if (line.capacity > MIN_LINE_BUF_LEN)
217     {
218       line.capacity = MIN_LINE_BUF_LEN;
219       char* str = realloc (line.str, line.capacity);
220       if (str)
221         line.str = str;
222     }
223   char* insertAt = line.str;
224   size_t restSize = line.capacity;
225   line.length = 0;
226   char* sp = 0;
227   while ((sp = fgets (insertAt, restSize, line.ifp)) != NULL)
228     {
229       line.length += strlen(insertAt);
230       /* read complete whole line */
231       if (line.str[line.length-1] == '\n'
232           || line.str[line.length-1] == '\r')   /* for mac? */
233         {
234           break;
235         }
236       else
237         {
238           /* to extend capacity for rest of line */
239           size_t newCapacity = line.capacity * 2 / 3;
240           char* str = realloc (line.str, newCapacity);
241           if (!str)
242             {
243               ccunit_log ("/* no more memory */");
244               break;
245             }
246           line.str = str;
247           restSize = newCapacity - line.capacity;
248           insertAt = str + line.capacity;
249           line.capacity = newCapacity;
250         }
251     }
252   if (!sp)
253     return 0;
254   /* chomp CR/LF */
255   char* tail = line.str + line.length - 1;
256   while (*tail == '\n' || *tail == '\r')        /* for dos/mac? */
257     {
258       line.length --;
259       *(tail --) = '\0';
260     }
261   line.lno ++;
262   return 1;
263 }
264
265 /**
266  * read contents of doc comment.
267  *
268  * @return comment string. or NULL when error occurred.
269  */
270 static char* readDocCommentContents ()
271 {
272   bool eoc = false;                             /* reach end of comment */
273   char* content = NULL;                         /* comment content */
274   size_t length = 0;                            /* content length */
275   char* start = NULL;                           /* start of content */
276   char* end = NULL;                             /* end of content */
277   ccunit_log ("readDocCommentContent");
278   start = line.str + 2;
279   while (!eoc)
280     {
281       ccunit_dbg ("read from:%lu: \"%s\"", line.lno, start);
282       /* skip white spaces */
283       for (; *start && isspace (*start); start ++)
284         ;
285       if (*start != '*')                        /* block comment '*' */
286         ;
287       else if (start[1] == '/')                 /* eoc */
288         ;
289       else                                      /* skip white spaces */
290         for (start ++; *start && isspace (*start); start ++)
291           ;
292       /* seek to eol or end of comment */
293       for (end = start; *end; end ++)
294         if (end[0] == '*' && end[1] == '/')
295           {
296             ccunit_log ("end of comment");
297             eoc = true;
298             break;
299           }
300       /* trim trailing white space */
301       for (end --; start < end; end --)
302         if (!isspace (*end))
303           {
304             end ++;
305             break;
306           }
307       /* did a comment exist? */
308       if (start < end)
309         {
310           int len = (int)(end - start);
311           char* newContent = realloc (content, length + len + 2);
312           if (!newContent)
313             {
314               ccunit_err ("no more memory");
315               break;
316             }
317           if (length > 0)
318             newContent[length ++] = ' ';        /* word space */
319           memcpy (newContent + length, start, len);
320           length += len;
321           newContent[length] = '\0';
322           content = newContent;
323           ccunit_log ("get: \"%*.*s\"", len, len, start);
324         }
325       if (eoc || !readline ())
326         break;
327       start = line.str;
328     }
329   ccunit_dbg ("comment content: \"%s\"", content);
330   return content;
331 }
332
333 /**
334  * read document comment.
335  *
336  * @return comment content if matched, or NULL if not matched.
337  */
338 static char* readDocComment ()
339 {
340   const char* cmnt = "/**";
341   if (strncmp (line.str, cmnt, strlen(cmnt)) != 0) /* not a comment */
342     ;
343   else if (line.str[3] == '*' || line.str[3] == '/') /* not doc */
344     ;
345   else
346     {
347       ccunit_dbg ("found doc comment:%lu: %s", line.lno, line.str);
348       return readDocCommentContents ();
349     }
350   return NULL;
351 }
352
353 /**
354  * get test case def.
355  *
356  * @param str comment string.
357  * @return group attribute object.
358  */
359 static char* getTestCaseName (const char* str)
360 {
361   static const char* const prefix = "TEST CASE:";
362   const size_t prefixLen = strlen (prefix);
363   const char* name = NULL;
364   if (strncasecmp (str, prefix, prefixLen) == 0)
365     {
366       for (name = str + prefixLen; *name; name ++)
367         if (!isspace (*name))
368           break;
369       if (!*name)
370         {
371           name = NULL;
372           ccunit_err ("no test case name: %s. near line %lu",
373                       str, line.lno);
374         }
375     }
376   else
377     ccunit_dbg ("not a test case name: %s", str);
378   return (char*)name;
379 }
380
381 /**
382  * get end of case string.
383  * @param str string.
384  * @return eoc string.
385  */
386 static char* getEndOfCase (const char* str)
387 {
388   static const char* const prefix = "END TEST CASE";
389   const size_t prefixLen = strlen (prefix);
390   const char* name = NULL;
391   if (strncasecmp (str, prefix, prefixLen) == 0)
392     {
393       name = str + prefixLen;
394       if (*name && !isspace (*name) && !ispunct (*name))
395         {
396           name = NULL;
397           ccunit_dbg ("not a end of test case: %s", str);
398         }
399       else
400         {
401           for (; *name; name ++)
402             if (!isspace (*name))
403               break;
404           if (!*name)
405             ;
406           else
407             ccunit_log ("end of test case: %s", name);
408         }
409     }
410   else
411     ccunit_dbg ("not a end of test case: %s", str);
412   return (char*)name;
413 }
414
415 /**
416  * read test fixturedef.
417  *
418  * @param type required type string.
419  * @param prefix required fixture name prefix.
420  * @param desc description.
421  * @return funcdef object.
422  */
423 static CCUnitTestFixtureDef* readFixture (const char* type,
424                                           const char* prefix,
425                                           const char* desc)
426 {
427   char* typ;
428   char* name;
429   ccunit_dbg ("read fixture: %s %s... from '%s'", type, prefix, line.str);
430   for (typ = line.str; *typ; typ ++)
431     if (!isspace (*typ))
432       break;
433   if (strncmp (typ, type, strlen (type)) != 0)
434     {
435       ccunit_dbg ("type mismatch: %s %s", type, typ);
436       return NULL;
437     }
438   name = typ + strlen (type);
439   if (*name && !isspace (*name))
440     {
441       ccunit_dbg ("type mismatch: %s %s", type, name);
442       return NULL;
443     }
444   for (;;)
445     {
446       for (; *name; name ++)
447         if (!isspace (*name))
448           break;
449       if (*name)
450         break;
451       if (!readline ())
452         {
453           ccunit_err ("unexpected EOF");
454           return NULL;
455         }
456     }
457   if (strncmp (name, prefix, strlen(prefix)) == 0)
458     {
459       char* tail;
460       for (tail = name + 1; *tail; tail ++)
461         if (isspace (*tail) || *tail == '(')
462           {
463             *tail = '\0';
464             break;
465           }
466       return newTestFixtureDef (type, name, desc);
467     }
468   else
469     ccunit_dbg ("name mismatch: %s %s", prefix, name);
470   return NULL;
471 }
472
473 /**
474  * read test case function.
475  *
476  * @param parent parent suite.
477  * @param cname test case name to read.
478  */
479 static void readTestCase (CCUnitTestSuiteDef* parent, const char* cname)
480 {
481   CCUnitTestSuiteDef* suite;
482   CCUnitTestCaseDef* testCase;
483   CCUnitTestFixtureDef* f = NULL;
484   char* name;
485   char* doc;
486   char* desc = NULL;
487   suite = ccunit_newTestSuiteDef (NULL);
488   if (!suite)
489     return;
490   testCase = newTestCaseDef (cname);
491   if (!testCase)
492     {
493       deleteTestDef (&suite->testdef);
494       return;
495     }
496   addTestDef (parent, &suite->testdef);
497   addTestDef (suite, &testCase->testdef);
498   while (readline ())
499     {
500       /* setUp function def */
501       if ((f = readFixture ("void", "setUp", desc)) != NULL)
502         {
503           if (!testCase->setUp)
504             testCase->setUp = f;
505           else
506             {
507               ccunit_err ("%s:%lu: setUp multiply defined %s... ignored.\n"
508                           "    previous definition is %s\n"
509                           "    perhaps missing /** test case: ... */",
510                           line.fname, line.lno, f->name, testCase->setUp->name);
511               deleteTestFixtureDef (f);
512             }
513           safe_free (desc);
514         }
515       /* tearDown function def */
516       else if ((f = readFixture ("void", "tearDown", desc)) != NULL)
517         {
518           if (!testCase->tearDown)
519             testCase->tearDown = f;
520           else
521             {
522               ccunit_err ("%s:%lu: tearDown multiply defined %s... ignored.\n"
523                           "    previous definition is %s\n"
524                           "    perhaps missing /** test case: ... */",
525                           line.fname, line.lno, f->name, testCase->tearDown->name);
526               deleteTestFixtureDef (f);
527             }
528           safe_free (desc);
529         }
530       /* if test fixture function def, then read as test fixture. */
531       else if ((f = readFixture ("void", "test", desc)) != NULL)
532         {
533           ccunit_addList (&testCase->fixtures, f);
534           safe_free (desc);
535         }
536       /* if current line is javaDoc comment, then read as description. */
537       else if ((doc = readDocComment ()) != NULL)
538         {
539           if ((name = getTestCaseName (doc)) != NULL)
540             {
541               ccunit_err ("%s:%lu: unbaranced end case comment '%s', "
542                           "need /** end case: %s */",
543                           line.fname, line.lno, doc, cname);
544               readTestCase (suite, name);
545               safe_free (doc);
546             }
547           else if ((name = getEndOfCase (doc)) != NULL)
548             {
549               ccunit_log ("exit test case: %s", testCase->testdef.name);
550               safe_free (doc);
551               break;
552             }
553           desc = doc;
554         }
555       else
556         ;
557     }
558   safe_free (desc);
559 }
560
561 /**
562  * read test suite def.
563  *
564  * @param parent parent suitedef.
565  */
566 static void readSuite (CCUnitTestSuiteDef* parent)
567 {
568   CCUnitTestFixtureDef* f;
569   const char* name;
570   char* doc;
571   char* desc = NULL;
572   while (readline ())
573     {
574       /* if current line is javaDoc comment, then read as description. */
575       if ((doc = readDocComment ()) != NULL)
576         {
577           if ((name = getTestCaseName (doc)) != NULL)
578             {
579               readTestCase (parent, name);
580               safe_free (doc);
581             }
582           else if ((name = getEndOfCase (doc)) != NULL)
583             {
584               ccunit_err ("%s:%lu: invalid end test case comment '%s'",
585                           line.fname, line.lno, doc);
586               safe_free (doc);
587             }
588           else
589             desc = doc;
590         }
591       else if ((f = readFixture ("void", "test", desc)) != NULL
592                || (f = readFixture ("void", "setUp", desc)) != NULL
593                || (f = readFixture ("void", "tearDown", desc)) != NULL)
594         {
595           ccunit_err ("%s:%lu: missing test case start comment '%s': ignored",
596                       line.fname, line.lno, line.str);
597           deleteTestFixtureDef (f);
598           safe_free (desc);
599         }
600       else
601         ;
602     }
603   safe_free (desc);
604 }
605
606 void ccunit_readSuite (const char* fname, CCUnitTestSuiteDef* parent)
607 {
608   if (strcmp (fname, "-") == 0) /* special file name '-' as stdin  */
609     {
610       line.ifp = stdin;
611       line.fname = "stdin";
612     }
613   else
614     {
615       line.ifp = fopen (fname, "r");
616       if (!line.ifp)                            /* open error */
617         {
618           ccunit_err ("can't open file '%s': %s.  skipped.\n",
619                       fname, strerror (errno));
620           return;
621         }
622       line.fname = fname;
623     }
624   readSuite (parent);
625   safe_free (line.str);
626   if (line.ifp != NULL && line.ifp != stdin)
627     fclose (line.ifp);
628   memset (&line, 0, sizeof (line));
629 }