releasing version 0.9.5-1
[debian/mimetic.git] / test / cutee.cxx
1 /***************************************************************************
2     copyright            : (C) by 2003-2004 Stefano Barbato
3     email                : stefano@codesink.org
4
5     $Id: cutee.cxx,v 1.2 2008-10-07 11:47:37 tat Exp $
6  ***************************************************************************/
7
8 /***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 #include <iostream>
17 #include <iterator>
18 #include <fstream>
19 #include <algorithm>
20 #include <string>
21 #include <list>
22 #include <ctype.h>
23 #include <unistd.h>
24 #include "cutee.h"
25
26 #define DEFAULT_RUNNER_EXT     __FILE__
27 #define RUNTEST_NAME        "runtest"
28 #define CPP_EXT            " cc cxx cpp c++ CC CXX CPP C++ "
29
30 using namespace std;
31
32 // c++ unit testing easy environment
33 const char* g_usage = 
34 "cutee, C++ Unit Testing Easy Environment, Ver. " CUTEE_VERSION  "\n\
35 Copyright (c) 2003-2004 by Stefano Barbato - All rights reserved.\n\
36 Usage: cutee [options]... testfile(s)\n\
37  -m     generates runtest source code\n\
38  -o     specify the output filename\n\
39  -p     generates autocutee.mk to include in your Makefile\n\
40  -k     generates autocutee.mk to include in your Makefile.am\n\n";
41
42 typedef list<string> StringList;
43
44 #define die(msg) do { do_die_if(1, msg, __LINE__); } while(0);
45 #define die_if(a, msg) do { do_die_if(a, msg, __LINE__); } while(0);
46 void do_die_if(int b, const string& msg, int line)
47 {
48     if(!b)
49         return;
50     cerr << "(" << line << ") " << msg << endl;
51     exit(1);
52 }
53 #define _( code ) of << code << endl
54
55 enum {     
56     MODE_RUNTEST, 
57     MODE_MAIN, 
58     MODE_AUTOMAKEFILE, 
59     MODE_MAKEFILE  
60 };
61
62 struct CmdLineOpts
63 {
64     string ifile, ofile, ext;
65     string class_name;
66     StringList ifList;
67     int mode;
68
69     CmdLineOpts()
70     : ext(DEFAULT_RUNNER_EXT),mode(MODE_RUNTEST)
71     {
72     }
73     void parse(int argc, char **argv)
74     {
75         int ret, i;
76         mode = MODE_RUNTEST; // default
77         while((ret=getopt(argc, argv, "mo:pk")) != -1)
78         {
79             switch(ret)
80             {
81             case 'o':
82                 ofile = optarg;
83                 break;
84             case 'm':
85                 mode = MODE_MAIN;
86                 break;
87             case 'k':
88                 mode = MODE_AUTOMAKEFILE;
89                 break;
90             case 'p':
91                 mode = MODE_MAKEFILE;
92                 break;
93             default:
94                 die(g_usage);
95             }
96         }
97         switch(mode)
98         {
99         case MODE_RUNTEST:
100             die_if( (argc - optind) != 1 || ofile.empty(), g_usage);
101             for(; optind < argc; optind++)
102                 ifile = argv[optind];
103             break;
104         case MODE_MAIN:
105             die_if( (argc - optind) != 0 || ofile.empty(), g_usage);
106             break;
107         case MODE_AUTOMAKEFILE:
108             die_if( (argc - optind) < 1 || ofile.empty(), g_usage);
109             for(i = 0; optind < argc; i++, optind++)
110                 ifList.push_back(argv[optind]);
111             break;
112         case MODE_MAKEFILE:
113             die_if( (argc - optind) < 1 || ofile.empty(), g_usage);
114             for(i = 0; optind < argc; i++, optind++)
115                 ifList.push_back(argv[optind]);
116             break;
117         };
118     }
119 };
120
121
122 class String2WordList
123 {
124     list<string> mList;
125     string mS;
126 public:
127     typedef list<string> WordList;
128     typedef list<string>::iterator iterator;
129     typedef list<string>::const_iterator const_iterator;
130
131     String2WordList(const string& s)
132     : mS(s)
133     {
134         if(mS.empty())
135             return;
136         split();
137     }
138     inline bool isWordChar(char c) const
139     {
140         return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || 
141             (c >= '0' && c <= '9') || (c == '_');
142     }
143     void split()
144     {
145         string word;
146         string::const_iterator beg = mS.begin(), end = mS.end();
147         for( ; beg != end; ++beg)
148         {
149             if(isWordChar(*beg))    
150                 word += *beg;
151             else {
152                 if(word.empty())
153                     continue;
154                 mList.push_back(word);
155                 word.erase(word.begin(), word.end());
156             }
157         }
158         if(!word.empty())
159             mList.push_back(word);
160     }
161     iterator begin()         {    return mList.begin();    }
162     iterator end()            {    return mList.end();    }
163     const_iterator begin() const    {    return mList.begin();    }
164     const_iterator end() const    {    return mList.end();    }
165 };
166
167 struct GenMain
168 {
169     GenMain(const string& ofqn)
170     : mFqn(ofqn)
171     {
172     }
173     void writeMain()
174     {
175         ofstream of(mFqn.c_str());
176         die_if(!of.is_open(), 
177             string("Unable to open output file: ") + mFqn);
178
179         _( "#include <iostream>" );
180         _( "#include <iomanip>" );
181         _( "#include \"cutee.h\"" );
182         _( "using namespace std;" );
183         _( "using namespace cutee;" );
184         _( "" );
185         _( "// static vars initialization" );
186         _( "CuteeTest* TestList::list[MAX_TEST_COUNT];" );
187         _( "int TestList::list_idx = 0;" );
188         _( "" );
189         _( "int main(int argc, char **argv) " );
190         _( "{" );
191         _( "\tRunner r(argc, argv);" );
192         _( "\tr.run();" );
193         _( "}" );
194     }
195 private:
196     string mFqn;
197 };
198
199 struct GenRunTest
200 {
201     GenRunTest(const string& ifqn, const string& ofqn)
202     : m_ifqn(ifqn), mFqn(ofqn)
203     {
204     }
205     void writeRunTestClass()
206     {
207         ofstream of(mFqn.c_str());
208         die_if(!of.is_open(), 
209             string("Unable to open output file: ") + mFqn);
210         string cn = m_className, fqcn;
211         if(m_nsList.empty())
212             fqcn = cn;
213         else {        
214             StringList::const_iterator 
215                     beg = m_nsList.begin(),
216                     end = m_nsList.end();
217             for(; beg != end; ++beg)
218                 fqcn += *beg + "::";
219             fqcn += cn;
220         }
221         _( "#include \"cutee.h\"" );
222         _( "#include \"" << m_ifqn << "\"" );
223         _( "using namespace cutee;" );
224         _( "struct run_" << cn << ": public " << fqcn);
225         _( "{" );
226         _( "  void run()" );
227         _( "  {" );
228         of << "    ";
229         _( "    setUp();" );
230         StringList::const_iterator 
231                     beg = m_fnList.begin(),
232                     end = m_fnList.end();
233         for(; beg != end; ++beg)
234             _( "    run_" << *beg << "();" );
235         _( "    tearDown();" );
236         _( "  }" );
237         _( "  uint count() { return " << m_fnList.size() << "; }" );
238         _( "};" );
239         _("static struct "<<cn<<"_add_to_list: public cutee::TestList");
240         _( "{" );
241         _( "  " << cn << "_add_to_list()" );
242         _( "  {" );
243         _( "  list[list_idx++] = new run_" << cn << ";" );
244         _( "  }" );
245         _( "} " << cn << "_addit;" );
246     }
247     int parseInputFile()
248     {
249         enum { 
250             START, GET_NAMESPACE_NAME, GET_CLASS_NAME, 
251             WAITING_FUNCTION, GET_FUNCTION_NAME
252         };
253         ifstream inf(m_ifqn.c_str());
254         die_if(!inf.is_open(), 
255             string("Unable to open input file: ") + m_ifqn);
256
257         int state = START;
258         string line;
259         while(getline(inf, line))
260         {
261             string w, uw, prev;
262             String2WordList s2wl(line);
263             String2WordList::iterator 
264                     beg = s2wl.begin(), end = s2wl.end();
265             while( beg != end )
266             {
267                 prev = w;
268                 w = *beg;
269                 switch(state)
270                 {
271                 case START:
272                     if(w == "namespace" && prev != "using")
273                         state = GET_NAMESPACE_NAME;
274                     else if(w == "TEST_CLASS")
275                         state = GET_CLASS_NAME;
276                     break;
277                 case GET_NAMESPACE_NAME:
278                     m_nsList.push_back(w);
279                     state = START;
280                     break;
281                 case GET_CLASS_NAME:
282                     m_className = w;
283                     state = WAITING_FUNCTION;
284                     break;
285                 case WAITING_FUNCTION:
286                     if(w == "TEST_FUNCTION")
287                         state = GET_FUNCTION_NAME;
288                     break;
289                 case GET_FUNCTION_NAME:
290                     m_fnList.push_back(w);
291                     state = WAITING_FUNCTION;
292                     break;
293                 default:
294                     die("Unknown state");
295                 }
296                 ++beg;
297             }
298         }
299         return !m_className.empty();
300     }
301 private:
302     string m_ifqn, mFqn, m_className;
303     StringList m_nsList, m_fnList;
304 };
305
306 struct GenMakefile
307 {
308     GenMakefile(const StringList& ifList, const string& ofqn, const string& ext)
309     : mFqn(ofqn), mExt(ext), mIfList(ifList)  
310     {
311     }
312     void writeMakefile()
313     {
314         string cc_exts = CPP_EXT;
315         ofstream of(mFqn.c_str());
316         die_if(!of.is_open(), 
317             string("Unable to open output file: ") + mFqn);
318
319         _( "# cutee autogen: begin" );
320         _( "CUTEE=./cutee" );
321
322         of << "object_files=";
323         StringList::const_iterator 
324                 beg = mIfList.begin(),
325                 end = mIfList.end();
326         for(; beg != end; ++beg)
327         {
328             GenRunTest grt(*beg, "");
329             if(grt.parseInputFile())
330                 of << stripExt(*beg) << "." << stripExt(mExt) 
331                     << ".o" << " ";
332             else if(cc_exts.find(" " + getExt(*beg) + " ") != string::npos)
333                 of << stripExt(*beg) << "." << "o" << " ";
334         }
335         of << endl;
336
337         beg = mIfList.begin(),    end = mIfList.end();
338         for(; beg != end; ++beg)
339         {
340             _( "" );
341             GenRunTest grt(*beg, "");
342             if(grt.parseInputFile())
343             {
344                 string dst = stripExt(*beg) + "." + mExt;
345                 of << dst << ": " << *beg << endl;
346                 of << "\t$(CUTEE) -o " << dst << " " << *beg 
347                 << endl;
348             }
349
350         }
351         of << endl;
352
353         _( "" );
354         _( RUNTEST_NAME ".cxx: cutee" );
355         _( "\t$(CUTEE) -m -o "RUNTEST_NAME".cxx" );
356         _( "" );
357         _( RUNTEST_NAME": autocutee.mk " RUNTEST_NAME ".o $(object_files)");
358         _( "\t$(CXX) $(CXXFLAGS) $(LDFLAGS) -o "RUNTEST_NAME" "RUNTEST_NAME".o $(object_files)");
359         _( "\t./"RUNTEST_NAME );
360         _( "" );
361         _( "# cutee autogen: end ");
362     }
363 private:
364     string getExt(const string& fqn)
365     {
366         string::size_type idx =    fqn.find_last_of('.');
367         if(idx != string::npos)
368             return string(fqn, ++idx);
369         else
370             return string();
371     }
372     string stripExt(const string& fqn)
373     {
374         string::size_type idx =    fqn.find_last_of('.');
375         if(idx != string::npos)
376             return string(fqn, 0, idx);
377         else
378             return fqn;
379     }
380     string mFqn, mExt;
381     StringList mIfList;
382 };
383
384 struct GenAutomakefile
385 {
386     GenAutomakefile(const StringList& ifList, const string& ofqn, const string& ext)
387     : mFqn(ofqn), mExt(ext), mIfList(ifList)
388     {
389     }
390     void writeMakefile()
391     {
392         ofstream of(mFqn.c_str());
393         die_if(!of.is_open(), 
394             string("Unable to open output file: ") + mFqn);
395
396         _( "# cutee autogen: begin" );
397         _( "CUTEE=./cutee" );
398
399         of << "t_runners=";
400         StringList::const_iterator 
401                 beg = mIfList.begin(),
402                 end = mIfList.end();
403         for(; beg != end; ++beg)
404         {
405             GenRunTest grt(*beg, "dummy");
406             if(grt.parseInputFile())
407                 of << stripPath(stripExt(*beg)) << "." << mExt << " ";
408         }
409         of << endl;
410
411         _( "" );
412         _( "%.cutee.cxx: $(srcdir)/%.h" );
413         _( "\t$(CUTEE) -o $@ $<");
414
415         _( "" );
416         _( "" );
417         _( RUNTEST_NAME ".cxx: cutee" );
418         _( "\t$(CUTEE) -m -o $@" );
419
420         _( "" );
421         _( RUNTEST_NAME "-clean:");
422         _( "\trm -f autocutee.mk cutee *.o *.cutee.cxx "RUNTEST_NAME" "RUNTEST_NAME".cxx");
423         _( "\ttouch autocutee.mk");
424
425         _( "" );
426         _( "EXTRA_PROGRAMS="RUNTEST_NAME );
427         _( RUNTEST_NAME "_SOURCES="RUNTEST_NAME".cxx $(test_files) $(t_runners)");
428         _( RUNTEST_NAME"_DEPENDENCIES=cutee autocutee.mk" );
429         _( "# cutee autogen: end ");
430     }
431 private:
432     string stripPath(const string& fqn)
433     {
434         string::size_type idx =    fqn.find_last_of('/');
435         if(idx != string::npos)
436             return string(fqn, ++idx);
437         else
438             return fqn;
439     }
440     string stripExt(const string& fqn)
441     {
442         string::size_type idx =    fqn.find_last_of('.');
443         if(idx != string::npos)
444             return string(fqn, 0, idx);
445         else
446             return fqn;
447     }
448     string mFqn, mExt;
449     StringList mIfList;
450 };
451 //
452 // main
453 //
454 int main(int argc, char** argv)
455 {
456     CmdLineOpts clo;
457     clo.parse(argc,argv);
458     GenRunTest* pGrt;
459     GenMain* pGm;
460     GenAutomakefile* pGamk;
461     GenMakefile* pGmk;
462
463
464     switch(clo.mode)
465     {
466     case MODE_RUNTEST:
467         // i'm using new/delete to avoid a warning (jump to case 
468         // label crosses initialization of ...) that throw an
469         // internal compiler error using gcc 3.2(linux)
470         pGrt = new GenRunTest(clo.ifile, clo.ofile);
471         if(pGrt->parseInputFile())
472             pGrt->writeRunTestClass();
473         delete pGrt;
474         break;
475     case MODE_MAIN:
476         pGm = new GenMain(clo.ofile);
477         pGm->writeMain();
478         delete pGm;
479         break;
480     case MODE_AUTOMAKEFILE:
481         pGamk = new GenAutomakefile(clo.ifList, clo.ofile, clo.ext);
482         pGamk->writeMakefile();
483         delete pGamk;
484         break;
485     case MODE_MAKEFILE:
486         pGmk = new GenMakefile(clo.ifList, clo.ofile, clo.ext);
487         pGmk->writeMakefile();
488         delete pGmk;
489         break;
490     default:
491         die("UNKNOWN MODE");
492         break;
493     }
494     return 0;
495 }
496
497