diff --git a/Makefile b/Makefile index 21eeb68eb..33eb503d6 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ OBJS=$(SRCS:%.cpp=%.o) all: ${OBJS} main.o g++ -Wall -g -o cppcheck $^ -test: ${OBJS} tests.o - g++ -Wall -g -o cppcheck_test $^ +test: ${OBJS} TestsRunner.o MiniCppUnit.o testmemleak.o + g++ -Wall -g -o testsrunner $^ clean: rm -f *.o cppcheck_test cppcheck diff --git a/MiniCppUnit.cpp b/MiniCppUnit.cpp new file mode 100644 index 000000000..b746704f1 --- /dev/null +++ b/MiniCppUnit.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2003-2004 Pau Arum� & David Garc�a + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "MiniCppUnit.h" + +#include + +#ifdef _MSC_VER +#include +namespace std +{ + template + inline bool isnan(T x) { + return _isnan(x) != 0; + } + template + inline bool isinf(T x) { + return _finite(x) == 0; + } +} +#endif + +TestsListener& TestsListener::theInstance() +{ + static TestsListener instancia; + return instancia; +} + +std::stringstream& TestsListener::errorsLog() +{ + if (_currentTestName) + _log << "\n" << errmsgTag_nameOfTest() << (*_currentTestName) << "\n"; + return _log; +} + +std::string TestsListener::logString() +{ + std::string aRetornar = _log.str(); + _log.str(""); + return aRetornar; +} +void TestsListener::currentTestName( std::string& name) +{ + _currentTestName = &name; +} +void TestsListener::testHasRun() +{ + std::cout << "."; + theInstance()._executed++; +} +void TestsListener::testHasFailed() +{ + std::cout << "F"; + theInstance()._failed++; + throw TestFailedException(); +} +void TestsListener::testHasThrown() +{ + std::cout << "E"; + theInstance()._exceptions++; +} +std::string TestsListener::summary() +{ + std::ostringstream os; + os << "\nSummary:\n" + << Assert::bold() << "\tExecuted Tests: " + << _executed << Assert::normal() << std::endl + << Assert::green() << "\tPassed Tests: " + << (_executed-_failed-_exceptions) + << Assert::normal() << std::endl; + if (_failed > 0) + { + os << Assert::red() << "\tFailed Tests: " + << _failed << Assert::normal() << std::endl; + } + if (_exceptions > 0) + { + os << Assert::yellow() << "\tUnexpected exceptions: " + << _exceptions << Assert::normal() << std::endl; + } + os << std::endl; + return os.str(); +} +bool TestsListener::allTestsPassed() +{ + return !theInstance()._exceptions && !theInstance()._failed; +} + + + +void Assert::assertTrue(char* strExpression, bool expression, + const char* file, int linia) +{ + if (!expression) + { + TestsListener::theInstance().errorsLog() << "\n" + << errmsgTag_testFailedIn() << file + << errmsgTag_inLine() << linia << "\n" + << errmsgTag_failedExpression() + << bold() << strExpression << normal() << "\n"; + TestsListener::theInstance().testHasFailed(); + } +} + +void Assert::assertTrueMissatge(char* strExpression, bool expression, + const char* missatge, const char* file, int linia) +{ + if (!expression) + { + TestsListener::theInstance().errorsLog() << "\n" + << errmsgTag_testFailedIn() << file + << errmsgTag_inLine() << linia << "\n" + << errmsgTag_failedExpression() + << bold() << strExpression << "\n" + << missatge<< normal() << "\n"; + TestsListener::theInstance().testHasFailed(); + } +} + + + +void Assert::assertEquals( const char * expected, const char * result, + const char* file, int linia ) +{ + assertEquals(std::string(expected), std::string(result), + file, linia); + +} +void Assert::assertEquals( const bool& expected, const bool& result, + const char* file, int linia ) +{ + assertEquals( + (expected?"true":"false"), + (result?"true":"false"), + file, linia); +} + +// floating point numbers comparisons taken +// from c/c++ users journal. dec 04 pag 10 +bool isNaN(double x) +{ + bool b1 = (x < 0.0); + bool b2 = (x >= 0.0); + return !(b1 || b2); +} + +double scaledEpsilon(const double& expected, const double& fuzzyEpsilon ) +{ + const double aa = fabs(expected)+1; + return (std::isinf(aa))? fuzzyEpsilon: fuzzyEpsilon * aa; +} +bool fuzzyEquals(double expected, double result, double fuzzyEpsilon) +{ + return (expected==result) || ( fabs(expected-result) <= scaledEpsilon(expected, fuzzyEpsilon) ); +} +void Assert::assertEquals( const double& expected, const double& result, + const char* file, int linia ) +{ + const double fuzzyEpsilon = 0.000001; + assertEqualsEpsilon( expected, result, fuzzyEpsilon, file, linia ); +} + +void Assert::assertEquals( const float& expected, const float& result, + const char* file, int linia ) +{ + assertEquals((double)expected, (double)result, file, linia); +} +void Assert::assertEquals( const long double& expected, const long double& result, + const char* file, int linia ) +{ + assertEquals((double)expected, (double)result, file, linia); +} +void Assert::assertEqualsEpsilon( const double& expected, const double& result, const double& epsilon, + const char* file, int linia ) +{ + if (isNaN(expected) && isNaN(result) ) return; + if (!isNaN(expected) && !isNaN(result) && fuzzyEquals(expected, result, epsilon) ) return; + + TestsListener::theInstance().errorsLog() + << errmsgTag_testFailedIn() << file + << errmsgTag_inLine() << linia << "\n" + << errmsgTag_expected() + << bold() << expected << normal() << " " + << errmsgTag_butWas() + << bold() << result << normal() << "\n"; + TestsListener::theInstance().testHasFailed(); +} + +int Assert::notEqualIndex( const std::string & one, const std::string & other ) +{ + int end = std::min(one.length(), other.length()); + for ( int index = 0; index < end; index++ ) + if (one[index] != other[index] ) + return index; + return end; +} + + +/** + * we overload the assert with string doing colored diffs + * + * MS Visual6 doesn't allow string by reference :-( + */ +void Assert::assertEquals( const std::string expected, const std::string result, + const char* file, int linia ) +{ + if(expected == result) + return; + + int indexDiferent = notEqualIndex(expected, result); + TestsListener::theInstance().errorsLog() + << file << ", linia: " << linia << "\n" + << errmsgTag_expected() << "\n" << blue() + << expected.substr(0,indexDiferent) + << green() << expected.substr(indexDiferent) + << normal() << "\n" + << errmsgTag_butWas() << blue() << "\n" + << result.substr(0,indexDiferent) + << red() << result.substr(indexDiferent) + << normal() << std::endl; + + TestsListener::theInstance().testHasFailed(); +} +void Assert::fail(const char* motiu, const char* file, int linia) +{ + TestsListener::theInstance().errorsLog() << + file << errmsgTag_inLine() << linia << "\n" << + "Reason: " << motiu << "\n"; + + TestsListener::theInstance().testHasFailed(); +} + + diff --git a/MiniCppUnit.h b/MiniCppUnit.h new file mode 100644 index 000000000..12b2650a8 --- /dev/null +++ b/MiniCppUnit.h @@ -0,0 +1,487 @@ +/* + * Copyright (c) 2003-2004 Pau Arum� & David Garc�a + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef MiniCppUnit_hxx +#define MiniCppUnit_hxx + +/** + * @mainpage + * miniCppUnit + * (C) 2003-2006 Pau Arumi & David Garcia + * + * @version 2.5 2006-03-14 + * - MS Visual compatibility: SConstruct ccflags, usage example, #ifdefs + * @version 2.4 2006-03-14 + * - exit test case after first failure + * - double and float comparison with fuzzy equals (using scalable epsilon) + * - have into account not a numbers + * - new ASSERT_EQUALS_EPSILON macro + * - more colors, and disabled when comiled in MS Visual + * - removed catalan location. + * - UsageExample.cxx now uses all macros and features + * @version 2.3 2006-02-13 added usage example and SConstruct + * @version 2.2 2004-11-28 code in english and tests suites + * @version 2.1 2004-11-04 char* especialization + * @version 2.0 2004-10-26 TestsFactory + * @version 1.0 2003-10-28 initial + * + * Example of use: + * + * @code + * #include "MiniCppUnit.hxx" + * class MyTests : public TestFixture + * { + * public: + * TEST_FIXTURE( MyTests ) + * { + * CAS_DE_TEST( testAddition ); + * // etc + * } + * void testAddition() + * { + * ASSERT_EQUALS( 4, 1+1+2 ); + * } + * // etc + * }; + * + * REGISTER_FIXTURE( MyTests ); + * @endcode + * @code + * int main() + * { + * return TestFixtureFactory::theInstance().runTests() ? 0 : -1; + * } + * @endcode + * Good things: + * + * - it's a tiny framework made up of two or three src files. + * => no need to install as a library + * - object oriented and makes use of several GoF patterns + * - very simple usage. Just needs to learn very few C macros + * - string asserts are simpler to use than cppunit + * - string asserts are enhanced with coloured diffs + * - concrete test classes are totally decoupled via static factory + * => no src file have to include them all. + * - it have test suite hierarchies + * - compatible with non-standard compliant VisualC6 + * (though not necessary good ;) + */ + +#include +#include +#include +#include + +/** + * A singleton class. + * Receives tests results and stores messages to the test log + * for later listing. + * It's a singleton for an easy global access from the 'Asserts' + * methods but it is probably asking for a refactoring in order to limit + * access only to TestFixtures + */ +class TestsListener +{ +public: + /** accessor to the global (static) singleton instance */ + static TestsListener& theInstance(); + std::stringstream& errorsLog(); + std::string logString(); + void currentTestName( std::string& name); + static void testHasRun(); + static void testHasFailed(); + static void testHasThrown(); + /** the human readable summary of run tests*/ + std::string summary(); + /** returns wheather all run tests have passed */ + static bool allTestsPassed(); + +private: + static const char* errmsgTag_nameOfTest() { return "Test failed: "; } + + /** constructor private: force the singleton to be wellbehaved ! */ + TestsListener() : _currentTestName(0) + { + _executed=_failed=_exceptions=0; + } + + std::string* _currentTestName; + std::stringstream _log; + unsigned _executed; + unsigned _failed; + unsigned _exceptions; +}; + +class TestFailedException +{ +}; + +/** + * Abstract class with interface that allows run a test. That is runTest + * and name. It is implemented by TestFixture and TestCase + * + * It does the 'Component' role in the 'Composite' patten + **/ +class Test +{ +public: + virtual ~Test(){} + /** run the test: exercice the code and check results*/ + virtual void runTest() = 0; + /** the test human-readable name */ + virtual std::string name() const = 0; +}; + + +/** + * This class is just a placeholder for all assert functions --as static methods. + * It is meant for being used just by the assert macros + */ +class Assert +{ + static const char * errmsgTag_testFailedIn() { return "Test failed in "; } + static const char * errmsgTag_inLine() { return ", line: "; }; + static const char * errmsgTag_failedExpression() { return "Failed expression: "; } + static const char * errmsgTag_expected() { return "Expected: "; } + static const char * errmsgTag_butWas() { return "But was: "; } + +public: +#ifdef _MSC_VER + static const char * blue() { return ""; } + static const char * green() { return ""; } + static const char * red() { return ""; } + static const char * normal() { return ""; } + static const char * bold() { return ""; } + static const char * yellow() { return ""; } +#else + static const char * blue() { return "\033[36;1m"; } + static const char * green() { return "\033[32;1m"; } + static const char * red() { return "\033[31;1m"; } + static const char * normal() { return "\033[0m"; } + static const char * bold() { return "\033[" "1m"; } + static const char * yellow() { return "\033[93;1m"; } +#endif + template + static void assertEquals( const AType& expected, const AType& result, + const char* file="", int linia=0 ) + { + if(expected != result) + { + TestsListener::theInstance().errorsLog() + << file << ", linia: " << linia << "\n" + << errmsgTag_expected() << " " << expected << " " + << errmsgTag_butWas() << " " << result << "\n"; + TestsListener::theInstance().testHasFailed(); + } + } + + static void assertTrue(char* strExpression, bool expression, + const char* file="", int linia=0); + + static void assertTrueMissatge(char* strExpression, bool expression, + const char* missatge, const char* file="", int linia=0); + + static void assertEquals( const char * expected, const char * result, + const char* file="", int linia=0 ); + + static void assertEquals( const bool& expected, const bool& result, + const char* file="", int linia=0 ); + + static void assertEquals( const double& expected, const double& result, + const char* file="", int linia=0 ); + + static void assertEquals( const float& expected, const float& result, + const char* file="", int linia=0 ); + + static void assertEquals( const long double& expected, const long double& result, + const char* file="", int linia=0 ); + + static void assertEqualsEpsilon( const double& expected, const double& result, const double& epsilon, + const char* file="", int linia=0 ); + + static int notEqualIndex( const std::string & one, const std::string & other ); + + /** + * we overload the assert with string doing colored diffs + * + * MS Visual6 doesn't allow string by reference :-( + */ + static void assertEquals( const std::string expected, const std::string result, + const char* file="", int linia=0 ); + + static void fail(const char* motiu, const char* file="", int linia=0); + + +}; + +/** + * A TestFixture is a class that contain TestCases --which corresponds to + * ConcreteTestFixture methods-- common objects uder tests, and setUp and + * tearDown methods which are automatically executed before and after each + * test case. + * + * Is the base class of ConcreteFixtures implemented by the framework user + * + * It does the 'Composite' role in the 'Composite' GoF pattern. + * Its composite children are TestCases, which wrapps the test methods. + * + * It is a template class parametrized by ConcreteTestFixture so that it can + * instantiate TestCase objects templatized with this same parameter: it needs the + * concrete class type for calling its non-static methods. + */ +template +class TestFixture : public Test +{ +protected: + + typedef ConcreteTestFixture ConcreteFixture; + typedef void(ConcreteTestFixture::*TestCaseMethod)(); + + /** + * Wrapper for the test methods of concrete TestFixtures. + * + * Makes the 'Leave' role in the 'Composite' GoF pattern because can't be + * be a composition of other tests. + * + * It's also a case of 'Command' pattern because it encapsules in an object + * certain functionality whose execution depends on some deferred entity. + */ + class TestCase : public Test + { + public: + TestCase(ConcreteFixture* parent, TestCaseMethod method, const std::string & name) : + _parent(parent), + _testCaseMethod(method), + _name(name) + { + } + /** calls TestFixture method. setUp and tearDown methods are called by + * its parent TestFixture (in its runTest method). + * it is robust to unexpected exceptions (throw) */ + void runTest() + { + TestsListener::theInstance().testHasRun(); + TestsListener::theInstance().currentTestName(_name); + try + { + (_parent->*_testCaseMethod)(); + } + catch( std::exception& error ) + { + TestsListener::theInstance().testHasThrown(); + TestsListener::theInstance().errorsLog() + << "std::exception catched by MiniCppUnit: \n" + << "what() : " + << Assert::yellow() << error.what() + << Assert::normal() << "\n"; + } + catch ( TestFailedException& failure) //just for skiping current test case + { + } + catch(...) + { + TestsListener::theInstance().testHasThrown(); + TestsListener::theInstance().errorsLog() + << "non standard exception catched by MiniCppUnit.\n"; + } + } + + /** the TestFixture method hame */ + std::string name() const + { + return _name; + } + + private: + ConcreteFixture* _parent; + TestCaseMethod _testCaseMethod; + std::string _name; + }; + //------------- end of class TestCase ---------------------------- + +private: + + typedef std::list TestCases; + TestCases _testCases; + std::string _name; + + void testsList() const + { + std::cout << "\n+ " << name() << "\n"; + for( TestCases::const_iterator it=_testCases.begin(); + it!=_testCases.end(); it++ ) + std::cout << " - "<< (*it)->name() << "\n"; + } + + +public: + virtual void setUp() {} + virtual void tearDown() {} + + std::string name() const + { + return _name; + }; + + TestFixture(const std::string& name="A text fixture") : _name(name) + { + } + + void afegeixCasDeTest(ConcreteFixture* parent, TestCaseMethod method, const char* name) + { + TestCase* casDeTest = new TestCase(parent, method, _name + "::" + name); + _testCases.push_back( casDeTest ); + } + /** calls each test after setUp and tearDown TestFixture methods */ + void runTest() + { + testsList(); + TestCases::iterator it; + for( it=_testCases.begin(); it!=_testCases.end(); it++) + { + setUp(); + (*it)->runTest(); + tearDown(); + } + } + /** TestCase that wrapps TestFixture methods are dynamically created and owned by + * the TestFixture. So here we clean it up*/ + ~TestFixture() + { + TestCases::iterator it; + for( it =_testCases.begin(); it!=_testCases.end(); it++) + delete (*it); + } +}; + + +/** + * This class is aimed to hold a creator method for each concrete TestFixture + */ +class TestFixtureFactory +{ +private: + /** Well behaved singleton: + * Don't allow instantiation apart from theInstance(), so private ctr.*/ + TestFixtureFactory() + { + } + typedef Test* (*FixtureCreator)(); + std::list _creators; +public: + /** Accessor to the (static) singleton instance */ + static TestFixtureFactory& theInstance() + { + static TestFixtureFactory theFactory; + return theFactory; + } + bool runTests() + { + std::list::iterator it; + for(it=_creators.begin(); it!=_creators.end(); it++) + { + FixtureCreator creator = *it; + Test* test = creator(); + test->runTest(); + delete test; + } + std::string errors = TestsListener::theInstance().logString(); + if (errors!="") std::cout << "\n\nError Details:\n" << errors; + std::cout << TestsListener::theInstance().summary(); + + return TestsListener::theInstance().allTestsPassed(); + } + void addFixtureCreator(FixtureCreator creator) + { + _creators.push_back( creator ); + } + +}; + +/** + * Macro a usar despr�s de cada classe de test + */ +#define REGISTER_FIXTURE( ConcreteTestFixture ) \ +\ +Test* Creador##ConcreteTestFixture() { return new ConcreteTestFixture; } \ +\ +class Registrador##ConcreteTestFixture \ +{ \ +public: \ + Registrador##ConcreteTestFixture() \ + { \ + TestFixtureFactory::theInstance().addFixtureCreator( \ + Creador##ConcreteTestFixture); \ + } \ +}; \ +static Registrador##ConcreteTestFixture estatic##ConcreteTestFixture; + + +/** + * Assert macros to use in test methods. An assert is a test condition + * we want to check. + */ +#define ASSERT_EQUALS( expected, result) \ + Assert::assertEquals( expected, result, __FILE__, __LINE__ ); + +#define ASSERT_EQUALS_EPSILON( expected, result, epsilon) \ + Assert::assertEqualsEpsilon( expected, result, epsilon, __FILE__, __LINE__ ); + +#define ASSERT( exp ) \ + Assert::assertTrue(#exp, exp, __FILE__, __LINE__); + +#define ASSERT_MESSAGE( exp, message ) \ + Assert::assertTrueMissatge(#exp, exp, message, __FILE__, __LINE__); + +#define FAIL( why ) \ + Assert::fail(#why, __FILE__, __LINE__); + +/** + * Macros that allows to write the constructor of the concrete TestFixture. + * What the constructor does is agregate a wrapper for each test case (method) + * As easy to write as this: + * + * @code + * class MyTests : public TestFixture + * { + * public: + * TEST_FIXTURE( MyTests ) + * { + * TEST_CASE( test ); + * // etc + * } + * void test() + * { + * ASSERT_EQUALS( 4, 1+1+2 ); + * } + * @endcode + */ + +#define TEST_FIXTURE( ConcreteFixture ) \ + ConcreteFixture() : TestFixture( #ConcreteFixture ) + +#define TEST_CASE( methodName ) \ + afegeixCasDeTest( this, &ConcreteFixture::methodName, #methodName ); + + + + + +#endif // MiniCppUnit_hxx diff --git a/TestsRunner.cpp b/TestsRunner.cpp new file mode 100644 index 000000000..43dacae4c --- /dev/null +++ b/TestsRunner.cpp @@ -0,0 +1,14 @@ +#include "MiniCppUnit.h" +#include "tokenize.h" + +bool ShowAll = true; +bool CheckCodingStyle = true; +bool Debug = false; + +int main() +{ + // Provide a dummy filename for the error messages + Files.push_back( std::string("test.cpp") ); + + return TestFixtureFactory::theInstance().runTests() ? 0 : -1; +} diff --git a/testmemleak.cpp b/testmemleak.cpp new file mode 100644 index 000000000..d6d2cdc66 --- /dev/null +++ b/testmemleak.cpp @@ -0,0 +1,46 @@ + +#include "tokenize.h" +#include "CheckMemoryLeak.h" +#include "MiniCppUnit.h" + +#include + +extern std::ostringstream errout; + +class TestMemleak : public TestFixture +{ +private: + void check( const char code[] ) + { + // Tokenize.. + tokens = tokens_back = NULL; + std::istringstream istr(code); + TokenizeCode( istr ); + SimplifyTokenList(); + + // Clear the error buffer.. + errout.str(""); + + // Check for memory leaks.. + CheckMemoryLeak(); + } + +public: + TEST_FIXTURE( TestMemleak ) + { + TEST_CASE( simple1 ); + } + + void simple1() + { + check( "void f()\n" + "{\n" + " int *a = new int[10];\n" + "}\n" ); + ASSERT_EQUALS( std::string("[test.cpp:4]: Memory leak: a\n"), errout.str() ); + } +}; + +REGISTER_FIXTURE( TestMemleak ) + +