diff --git a/Make/debugtests.bat b/Make/debugtests.bat index 056ea10463e..816a9c6fe86 100755 --- a/Make/debugtests.bat +++ b/Make/debugtests.bat @@ -2,6 +2,10 @@ echo osgversion osgversion more memleaks.log +echo osgunittests +osgunittests +more memleaks.log + echo sgv osgcool.osg sgv osgcool.osg more memleaks.log diff --git a/Make/makedefs b/Make/makedefs index 8517a2491fa..bb0fa070e98 100644 --- a/Make/makedefs +++ b/Make/makedefs @@ -93,7 +93,7 @@ ifeq ($(COMPILER),gnu) INC += -I/usr/local/glut-3.7/include DEF += -W -Wall -fPIC -fpermissive OPTF = -O2 - DBGF = -g + DBGF = -g -DOSG_COMPILE_UNIT_TESTS SHARED = -shared -fPIC ifeq ($(ARCH),64) ARCHARGS = -m64 @@ -127,7 +127,7 @@ else INC += DEF += -features=extensions OPTF = -xO4 - DBGF = -g + DBGF = -g -DOSG_COMPILE_UNIT_TESTS SHARED = -G ifeq ($(ARCH),64) ARCHARGS = -xarch=v9 @@ -166,7 +166,7 @@ ifeq ($(OS),IRIX) -DEBUG:woff=1682 -DEBUG:woff=3303\ -MDupdate $(MAKEDEPEND) OPTF = -O2 - DBGF = -g + DBGF = -g -DOSG_COMPILE_UNIT_TESTS SHARED = -shared ARCH = 32 ifeq ($(ARCH),64) @@ -216,7 +216,7 @@ endif INC += DEF += -W -Wall OPTF = -O2 - DBGF = -g -DOSG_USE_MEMORY_MANAGER + DBGF = -g -DOSG_COMPILE_UNIT_TESTS -DOSG_USE_MEMORY_MANAGER SHARED = -shared ARCHARGS = LINKARGS = -L/usr/X11R6/lib @@ -250,7 +250,7 @@ ifeq ($(OS),FreeBSD) INC += -I/usr/local/include -I/usr/X11R6/include DEF += -W -Wall OPTF = -O2 - DBGF = -g + DBGF = -g -DOSG_COMPILE_UNIT_TESTS SHARED = -shared ARCHARGS = LINKARGS = -L/usr/X11R6/lib -L/usr/local/lib -rpath /usr/local/lib @@ -274,7 +274,7 @@ ifeq ($(OS),Darwin) INC += -I/usr/include DEF += -Wall -D__DARWIN_OSX__ OPTF = -O2 - DBGF = -g + DBGF = -g -DOSG_COMPILE_UNIT_TESTS DEPARG = -M $(DEF) SHARED = -shared ARCHARGS = @@ -316,7 +316,7 @@ ifeq ($(OS),CYGWIN) INC += DEF += -DWIN32 -W -Wall OPTF = -O2 - DBGF = -g + DBGF = -g -DOSG_COMPILE_UNIT_TESTS SHARED = -shared\ -Wl,--export-all-symbols \ -Wl,--output-def,lib$(TARGET_BASENAME).def \ @@ -381,7 +381,7 @@ ifeq ($(OS),MINGW) DEF += -DWIN32 -Wall # -W OPTF = -O2 - DBGF = -g + DBGF = -g -DOSG_COMPILE_UNIT_TESTS SHARED = -shared\ -Wl,--export-all-symbols \ -Wl,--output-def,lib$(TARGET_BASENAME).def \ @@ -457,7 +457,7 @@ ifeq ($(OS),HP-UX) OPTF = -O2 # gcc 3.1 uses DWARF as default, my GDB might not yet support this... # at least I've got problems everywhere - DBGF = -g -gstabs+ + DBGF = -g -gstabs+ -DOSG_COMPILE_UNIT_TESTS SHARED = -shared -fPIC ARCH = 32 ifeq ($(ARCH),64) diff --git a/Make/makedirdefs b/Make/makedirdefs index 3ded395ff17..d00c62efcae 100644 --- a/Make/makedirdefs +++ b/Make/makedirdefs @@ -93,6 +93,7 @@ DEMOS_DIRS = \ osgtexture1D\ osgtexture2D\ osgtexture3D\ + osgunittests\ osgviews\ osgversion\ sgv diff --git a/VisualStudio/Demos/osgunittests/osgunittests.dsp b/VisualStudio/Demos/osgunittests/osgunittests.dsp new file mode 100644 index 00000000000..a527c8ca22f --- /dev/null +++ b/VisualStudio/Demos/osgunittests/osgunittests.dsp @@ -0,0 +1,95 @@ +# Microsoft Developer Studio Project File - Name="Demo osgunittests" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=Demo osgunittests - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "osgunittests.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "osgunittests.mak" CFG="Demo osgunittests - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Demo osgunittests - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "Demo osgunittests - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Demo osgunittests - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GR /GX /O2 /I "../../../include" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 /nologo /subsystem:console /pdb:none /machine:I386 /out:"../../../bin/osgunittests.exe" /libpath:"../../../lib" + +!ELSEIF "$(CFG)" == "Demo osgunittests - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MDd /W3 /Gm /vd0 /GR /GX /Zi /Od /I "../../../include" /D "_CONSOLE" /D "_MBCS" /D "FL_DLL" /D "WIN32" /D "_DEBUG" /FR /YX /FD /c +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcmt" /out:"../../../bin/osgunittestsd.exe" /pdbtype:sept /libpath:"../../../lib" +# SUBTRACT LINK32 /incremental:no + +!ENDIF + +# Begin Target + +# Name "Demo osgunittests - Win32 Release" +# Name "Demo osgunittests - Win32 Debug" +# Begin Source File + +SOURCE=..\..\..\src\Demos\osgunittests\osgunittests.cpp +# End Source File +# End Target +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Project diff --git a/VisualStudio/VisualStudio.dsw b/VisualStudio/VisualStudio.dsw index e23edf17a87..e110f759ac4 100644 --- a/VisualStudio/VisualStudio.dsw +++ b/VisualStudio/VisualStudio.dsw @@ -279,6 +279,27 @@ Package=<4> ############################################################################### +Project: "Demo osgconv"=.\Demos\osgunittests\osgunittests.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name Core osg + End Project Dependency + Begin Project Dependency + Project_Dep_Name Core osgDB + End Project Dependency + Begin Project Dependency + Project_Dep_Name Core osgUtil + End Project Dependency +}}} + +############################################################################### + Project: "Demo osgcopy"=.\Demos\osgcopy\osgcopy.dsp - Package Owner=<4> Package=<5> diff --git a/VisualStudio/osg/osg.dsp b/VisualStudio/osg/osg.dsp index 1ba9d81ba3c..871b436b127 100755 --- a/VisualStudio/osg/osg.dsp +++ b/VisualStudio/osg/osg.dsp @@ -413,6 +413,14 @@ SOURCE=..\..\src\osg\BlendFunc.cpp # End Source File # Begin Source File +SOURCE=..\..\src\osg\UnitTestFramework.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\src\osg\Vec3.cpp +# End Source File +# Begin Source File + SOURCE=..\..\src\osg\Version.cpp # End Source File # Begin Source File @@ -805,6 +813,10 @@ SOURCE=..\..\Include\Osg\Types # End Source File # Begin Source File +SOURCE=..\..\Include\Osg\UnitTestFramework +# End Source File +# Begin Source File + SOURCE=..\..\Include\Osg\Vec2 # End Source File # Begin Source File diff --git a/include/osg/UnitTestFramework b/include/osg/UnitTestFramework new file mode 100644 index 00000000000..fbae95d71e9 --- /dev/null +++ b/include/osg/UnitTestFramework @@ -0,0 +1,565 @@ +//C++ header - Open Scene Graph - Copyright (C) 1998-2002 Robert Osfield +//Distributed under the terms of the GNU Library General Public License (LGPL) +//as published by the Free Software Foundation. + +#ifndef OSG_UNITTESTFRAMEWORK +#define OSG_UNITTESTFRAMEWORK 1 + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace osgUtx{ + +class TestVisitor; + +/** +Test, an abstract base class, is the Composite pattern's \em component +class for our graph of test cases, and defines the basic interface +for all Test components. It is a referent, and may be pointed +to by an osg::ref_ptr. +*/ +class SG_EXPORT Test: public osg::Referenced +{ +public: + + typedef TestVisitor Visitor; // Test is redundant + + Test( const string& sName ) : _name( sName ) {} + + const std::string& name() const { return _name; } + + virtual bool accept( Visitor& ) = 0; + +private: + + std::string _name; +}; + + +/** +TestContext wraps up information which is passed to tests as they are run, +and may contain test-specific information or 'global' test objects, such +as an output stream for verbose output during the running of tests. + +\todo Improve the output stream code by providing a filtering stream. +*/ +class SG_EXPORT TestContext +{ +public: + + TestContext(); + + bool shouldStop() { return false; } + bool isVerbose() { return true; } + + enum TraceLevel{ + Off, ///< All tracing turned off + Results, ///< Output results only + Full ///< Full test diagnostic output + }; + + void setTraceLevel(TraceLevel tl); + TraceLevel getTraceLevel() const; + + std::ostream& tout(TraceLevel tl=Full) const; + +private: + + TestContext(const TestContext&); + TestContext operator=(const TestContext&); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + + class TraceStream{ + + public: + TraceStream(std::ostream& o=cout, TraceLevel tl=Results); + ~TraceStream(); + + void setTraceLevel(TraceLevel tl); + TraceLevel getTraceLevel() const; + + std::ostream& stream(TraceLevel tl); + + private: + + TraceLevel _traceLevel; + std::ostream* _outputStreamPtr; + std::ofstream _nullStream; + }; + +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + + mutable TraceStream _tout; + +}; + + +class TestSuite; +class TestCase; + +/** +Visits while maintaining the current hierarchical context. Also allows +the traversal to be short-cicuited at any point during the visitation. +*/ +class TestVisitor +{ +public: + + //..Should we enter this node and its children? + virtual bool visitEnter( TestSuite* ) { return true; } + + //..Returns true to continue to next Leaf + virtual bool visit( TestCase* ) = 0; + + //..Returns true to continue to next Composite + virtual bool visitLeave( TestSuite* ) { return true; } +protected: + + TestVisitor() {} + TestVisitor( const TestVisitor& ) {} + virtual ~TestVisitor() {} +}; + +/** +TestCase, is the supplies the interface for a Composite pattern's +\em leaf class, though it is not a leaf in itself. +*/ +class TestCase : public Test +{ +public: + + typedef TestContext Context; // Test in TestContext? is redundant + + TestCase( const std::string& sName ) : Test( sName ) {} + + virtual bool accept( Visitor& v ) { return v.visit( this ); } + + virtual void run( const Context& ) = 0; // Subclass OSGUTX_EXPORT Responsibility +}; + +/** +Base class catchable for the exception's which may be thrown to +indicate problems during the run of a TestCase. +*/ +class TestX +{ +public: + + TestX(const std::string& s):_what(s) {} + virtual ~TestX() {} + + const std::string& what() const { return _what; } + +private: + std::string _what; +}; + +/** +A TestFailureX indicates a failure in the tested component. +*/ +class TestFailureX: public TestX +{ +public: + TestFailureX(const std::string& s):TestX(s) {} +}; + +/** +A TestErrorX indicates an error while testing a component, +which prevents the test from being run; it does not indicate +a problem with the component, but rather a problem during the +run which prevents the component from being tested. +*/ +class TestErrorX: public TestX +{ +public: + TestErrorX(const std::string& s):TestX(s) {} +}; + +/** +TestCase_ is a class template for a leaf TestCase, which allows TestFixture +classes to be easily collected into the tree of tests, and have their public +test methods called. It is worth noting that, for a given TestCase_, an +instance of the test fixture class will be constructed pior to the +test method being called, and destructed afterwards. This prevents 'leakage' +of information from one test case to the next. +*/ +template< typename FixtureT > +class TestCase_ : public TestCase +{ + typedef void (FixtureT::*TestMethodPtr)( const Context& ); + +public: + + // Constructor adds the TestMethod pointer + TestCase_( const std::string& sName, TestMethodPtr pTestMethod ) : + TestCase( sName ), + _pTestMethod( pTestMethod ) + { + } + + // Create a TestFixture instance and invoke TestMethod? + virtual void run( const Context& ctx ) + { + ( FixtureT().*_pTestMethod )( ctx ); + } + +private: + + TestMethodPtr _pTestMethod; +}; + +/** +A TestSuite is the \em composite component of the Composite pattern, +and allows aggregation of Tests into hierarchies. +*/ +class SG_EXPORT TestSuite : public Test +{ +public: + + TestSuite( const std::string& name ); + + /** Adds a Test to the suite. */ + void add( Test* pTest ); + + /** + @returns The immediate child denoted by name, or 0 if not found. + */ + Test* findChild(const std::string& name); + + virtual bool accept( Test::Visitor& v ); + +private: + + typedef std::vector< osg::ref_ptr > Tests; + Tests _tests; // Collection of Suites and/or Cases +}; + +/** +TestGraph is a singleton providing central access to the tree of tests; +primarily, it provides access to the root suite. +*/ +class TestGraph +{ + +public: + + static TestGraph& instance(); + + /** + @return a pointer to the root TestSuite. + */ + TestSuite* root(); + + /** + A utility function for accessing an arbitrary quite by pathname, relative to + the suite 'tsuite' (defaults to root if null), and with the option of creating + the \em TestSuite designated by \em path, if it does not already exist. + + This method may return 0 if the suite either cannot be found (and createIfNecssary + is 0), or the first component of \em path is not the same as the name of the + TestSuite \em tsuite. + + This was written to aid the auto-registration of tests at specific points in + the test tree, where the tests' AutoRegistrationAgents may be distributed across + several files, and cannot be guaranteed to run in a given order. E.g. You cannot + register a test "root.osg.MyTest" unless you know that the the suite "root.osg" + already exists. + + + @param path The name of the TestSuite to return. + @param tsuite The suite to 'start from'. Path is relative to this + suite (defaults to root suite). + @param createIfNecessary Optionally create the TestSuite(s) denoted by path if + they do not exist. + */ + TestSuite* suite(const std::string& path, TestSuite* tsuite = 0,bool createIfNecessary = false); + +private: + + /** + Does the same job as the version of suite listed above, but the path + is passed in as components in a list, represented by the iterator parameters. + */ + TestSuite* suite( + list::iterator it, + list::iterator end, + TestSuite* tsuite, bool createIfNecessary); + + TestGraph(); + + TestGraph(const TestGraph&); + TestGraph& operator=(const TestGraph&); + + osg::ref_ptr root_; + +}; + + +/** +Maintains a string that when accessed in the "visit" member, returns the +current qualified TestSuite path. +*/ +class SG_EXPORT TestQualifier : public TestVisitor +{ + enum { SEPCHAR = '.' }; + +public: + + // Entering a composite: Push its name on the Path + virtual bool visitEnter( TestSuite* pSuite ); + + // Leaving a composite: Pop its name from the Path + virtual bool visitLeave( TestSuite* pSuite ); + + // Provide read-only access to the current qualifier + const string& currentPath() const; + +private: + + std::string _path; // Current qualifier +}; + +/** +QualifiedTestPrinter prints to standard output a list of fully +qualified tests. +*/ +class SG_EXPORT QualifiedTestPrinter : public TestQualifier +{ +public: + + + virtual bool visit( TestCase* pTest ); +}; + +/** +A TestRecord records the output of a given test case, i.e. its start/stop time, +its result, and a textual description of any problems. + +\todo Consider adding accessor methods if necessary, to get the details + stored in the TestRecord. +*/ +class SG_EXPORT TestRecord +{ + public: + + void start(); + void stop(); + void log(const TestFailureX& e); + void log(const TestErrorX& e); + void log(const std::exception& e); + void log(const std::string& s); + + // Default copy construction and assignment are OK + + // FIXME: Add accessors? + + private: + + // Onlye a TestReport can create a TestRecord + friend class TestReport; + TestRecord(const string& name); + + enum Result{ + Success,Failure,Error + }; + + friend std::ostream& operator<<(std::ostream& o,const TestRecord& tr); + + static osg::Timer timer_; // To time tests + + std::string name_; + osg::Timer_t start_; + osg::Timer_t stop_; + Result result_; + std::string problem_; + +}; + +/** +A TestReport represents the complete set of results (TestRecords) for a +given test run. + +\todo Add support for printing the test report in various formats: + e.g. text, XML, CSV +*/ +class SG_EXPORT TestReport +{ +public: + + TestRecord& createRecord(const string& s){ + _records.push_back(TestRecord(s)); + return _records.back(); + } + +private: + list _records; + +}; + + + + + + + +/** +A TestRunner is a visitor which will run specified tests as it traverses the +test graph. + +\todo Consider an accessor method to get at the TestReport if necessary. +*/ +class SG_EXPORT TestRunner : public TestQualifier +{ +public: + + TestRunner( TestContext& ctx ); + + /** + Tests may be specified by partial names. E.g. specifiying "root" + will run all tests below root, i.e. all tests. + Specifiying "root.osg" will run all tests below \em root.osg. + Specifying "root.osg.de" will run all tests (and suites) below + \em root.osg with names beginning with the \em de. + */ + void specify( const string& sQualifiedName ); + + bool visitEnter( TestSuite* pSuite ); + bool visit( TestCase* pTest ); + bool visitLeave( TestSuite* pSuite ); + + +protected: + + void perform( TestCase* pTest ); + +private: + + TestReport _db; // Results + TestContext& _ctx; // The Global Testing Context + std::vector _tests; // Specified Tests +}; + +} + +/** +Starts a TestSuite singleton function +@see OSGUTX_ADD_TESTCASE, OSGUTX_END_TESTSUITE +*/ +#define OSGUTX_BEGIN_TESTSUITE( tsuite ) \ + osgUtx::TestSuite* tsuite##_TestSuite() \ + { \ + static osg::ref_ptr s_suite = 0; \ + if ( s_suite == 0 ) { \ + s_suite = new osgUtx::TestSuite( #tsuite ); + + + +/** +Adds a test case to a suite object being created in a TestSuite singleton function. +@see OSGUTX_BEGIN_TESTSUITE, OSGUTX_END_TESTSUITE +*/ +#define OSGUTX_ADD_TESTCASE( tfixture, tmethod ) \ + s_suite->add( new osgUtx::TestCase_( \ + #tmethod, &tfixture::tmethod ) ); + +/** +Ends a TestSuite singleton function +@see OSGUTX_BEGIN_TESTSUITE, OSGUTX_ADD_TESTCASE +*/ +#define OSGUTX_END_TESTSUITE \ + } \ + return s_suite.get(); \ + } + +/** Define a TestSuite accessor */ +#define OSGUTX_TESTSUITE( tsuite ) \ + tsuite##_TestSuite() + + +/** +Adds a suite to a suite - allows composition of test suites. +@see OSGUTX_BEGIN_TESTSUITE, OSGUTX_END_TESTSUITE +*/ +#define OSGUTX_ADD_TESTSUITE( childSuite ) \ + s_suite->add( childSuite##_TestSuite() ); + + +/** Autoregister a testsuite with the root suite at startup */ +#define OSGUTX_AUTOREGISTER_TESTSUITE( tsuite ) \ + static osgUtx::TestSuiteAutoRegistrationAgent tsuite##_autoRegistrationObj__( tsuite##_TestSuite() ); + +/** Auto register a testsuite with at designated point in the suite graph at startup */ +#define OSGUTX_AUTOREGISTER_TESTSUITE_AT( tsuite , path ) \ + static osgUtx::TestSuiteAutoRegistrationAgent tsuite##_autoRegistrationObj__( tsuite##_TestSuite(), #path ); + +namespace osgUtx{ + +/** +A helper struct to perform automatic registration at program startup; not for +direct use, it should be used via the following macros. (It's a secret agent :-) + +@see OSGUTX_AUTOREGISTER_TESTSUITE, OSGUTX_AUTOREGISTER_TESTSUITE_AT +*/ +struct TestSuiteAutoRegistrationAgent +{ + TestSuiteAutoRegistrationAgent(TestSuite* tsuite, const char* path = 0) + { + if( ! path ) path = "root"; + + // Find the suite named in 'path', create it if necessary + TestSuite *regSuite = osgUtx::TestGraph::instance().suite( path, 0, true ); + + if(!regSuite){ + std::cerr<<"Warning, unable to register test suite named \""<name()<<"\" at " + <add(tsuite); + } +}; + +} + +/** +OSGUTX_TEST_F is a convenience macro, analogous to assert(), which will +throw an osgUtx::TestFailureX if \em expr evaluates to false; this should be +used to test for failure in a given test, as opposed to an actual error +in the test owing to some other reason than the tested code being faulty. + +The exception will indicate the file and line number of the failed expression, +along with expression itself. +*/ +#define OSGUTX_TEST_F( expr ) \ + if( !(expr) ){ \ + std::stringstream ss; \ + ss<< #expr <<" failure: "<<__FILE__<<", line "<<__LINE__< + + +int main( int /*argc*/, char** /*argv*/ ) +{ +// cout<<"***** Qualified Tests ******"<accept( printer ); +// + + cout<accept( runner ); + + return 0; +} diff --git a/src/osg/Makefile b/src/osg/Makefile index 3911dc71c88..56a6e192378 100644 --- a/src/osg/Makefile +++ b/src/osg/Makefile @@ -82,7 +82,9 @@ CXXFILES =\ TextureCubeMap.cpp\ Timer.cpp\ Transform.cpp\ + UnitTestFramework.cpp\ Version.cpp\ + Vec3.cpp\ Viewport.cpp\ DEF += -DSG_LIBRARY diff --git a/src/osg/UnitTestFramework.cpp b/src/osg/UnitTestFramework.cpp new file mode 100644 index 00000000000..d30f8600ef5 --- /dev/null +++ b/src/osg/UnitTestFramework.cpp @@ -0,0 +1,383 @@ +#include + +#include + +namespace osgUtx +{ + +////////////////////////////////////////////////////////////////////////////// + +TestContext::TestContext() +{ +} + +void TestContext::setTraceLevel(TraceLevel tl) +{ + _tout.setTraceLevel(tl); +} + +TestContext::TraceLevel TestContext::getTraceLevel() const +{ + return _tout.getTraceLevel(); +} + +std::ostream& TestContext::tout(TraceLevel tl=Full) const +{ + return _tout.stream(tl); +} + +////////////////////////////////////////////////////////////////////////////// + + +TestContext::TraceStream::TraceStream(std::ostream& o, TraceLevel tl): + _traceLevel(tl), + _outputStreamPtr(&o), +#if defined(WIN32) && !(defined(__CYGWIN__) || defined(__MINGW32__)) + _nullStream("nul") +#else + _nullStream("/dev/null") +#endif +{ +} + +TestContext::TraceStream::~TraceStream() +{ + _nullStream.close(); +} + +void TestContext::TraceStream::setTraceLevel(TraceLevel tl) +{ + _traceLevel = tl; +} + +TestContext::TraceLevel TestContext::TraceStream::getTraceLevel() const +{ + return _traceLevel; +} + +std::ostream& TestContext::TraceStream::stream(TestContext::TraceLevel tl) +{ + if(_traceLevel >= tl){ + return *_outputStreamPtr; + } + return _nullStream; +} + +////////////////////////////////////////////////////////////////////////////// + +TestGraph& TestGraph::instance() +{ + static TestGraph instance_; + return instance_; +} + +TestSuite* TestGraph::root() +{ + return root_.get(); +} + +TestSuite* TestGraph::suite(const std::string& path, TestSuite* tsuite, bool createIfNecessary) +{ + using namespace std; + + list pathComponents; + + string::const_iterator it1 = path.begin(); + string::const_iterator it2 = it1; + + // Dissect the path into it's constituent components + do{ + + while( *it2 != '.' && it2 != path.end() ) ++it2; + + // Consider a check for "" empty strings? + pathComponents.push_back( string(it1,it2) ); + + if( it2 != path.end()) ++it2; + + it1 = it2; + + }while( it2 != path.end()); + + return suite(pathComponents.begin(), pathComponents.end(), + tsuite, createIfNecessary); + +} + +TestSuite* TestGraph::suite( + list::iterator it, + list::iterator end, + TestSuite* tsuite, bool createIfNecessary) +{ + using namespace std; + + if( ! tsuite) tsuite = root(); + + // Make sure these tie up + if(*it != tsuite->name()) return 0; + + ++it; + if(it == end) return tsuite; + + Test* child = tsuite->findChild(*it); + + if(child){ + + // We've found a child with the right name. But is it a + // test suite? + + if(TestSuite* childSuite = dynamic_cast(child)){ + return suite(it, end, childSuite, createIfNecessary); + } + + // We could return 0 here, to indicate that someone is + // trying to add a TestSuite named 'xxx' to a suite with a + // Test already named 'xxx'. But we don't enforce uniqueness + // the other way round, so we don't do it this way round + // either. Carry on as normal, and create a TestSuite of + // the same name if createIfNecessary is true. + + } + + if(createIfNecessary){ + + TestSuite* childSuite = new TestSuite(*it); + tsuite->add(childSuite); + return suite(it, end, childSuite, createIfNecessary); + } + + return 0; +} + +TestGraph::TestGraph(): root_(new TestSuite("root")) +{ +} + + +////////////////////////////////////////////////////////////////////////////// + +bool TestQualifier::visitEnter( TestSuite* pSuite ) +{ + _path.append( pSuite->name() ); + _path += SEPCHAR; + return true; +} + +// Leaving a composite: Pop its name from the Path +bool TestQualifier::visitLeave( TestSuite* pSuite ) +{ + assert( _path.rfind( pSuite->name() + static_cast(SEPCHAR)) + == _path.size() - pSuite->name().size() - 1); + + _path.erase( _path.size() - pSuite->name().size() -1 ); + return true; +} + +// Provide read-only access to the current qualifier +const string& TestQualifier::currentPath() const +{ + return _path; +} + +////////////////////////////////////////////////////////////////////////////// + +osg::Timer TestRecord::timer_; + +void TestRecord::start() +{ + start_ = timer_.tick(); +} + +void TestRecord::stop() +{ + stop_ = timer_.tick(); +} + +void TestRecord::log(const TestFailureX& e) +{ + stop(); + result_ = Failure; + problem_ = e.what(); +} + +void TestRecord::log(const TestErrorX& e) +{ + stop(); + result_ = Error; + problem_ = e.what(); +} + +void TestRecord::log(const std::exception& e) +{ + stop(); + result_ = Error; + problem_ = e.what(); +} + +void TestRecord::log(const std::string& s) +{ + stop(); + result_ = Error; + problem_ = s; +} + +TestRecord::TestRecord(const string& name): + name_(name), + start_(0), + stop_(0), + result_(Success), + problem_("No problem") +{ +} + +std::ostream& operator<<(std::ostream& o,const TestRecord& tr) +{ + if(tr.result_ == TestRecord::Success) o<<"pass"; + else if(tr.result_ == TestRecord::Failure) o<<"fail"; + else o<<"error"; + + o<<"\t"<name() ) ) != _tests.end()) perform( pTest ); + + return !_ctx.shouldStop(); +} + +bool TestRunner::visitLeave( TestSuite* pSuite ) +{ + TestQualifier::visitLeave( pSuite ); + return !_ctx.shouldStop(); +} + +void TestRunner::perform( TestCase* pTest ) +{ + TestRecord& record = _db.createRecord( currentPath() + pTest->name() ); + + try + { + record.start(); + pTest->run( _ctx ); + record.stop(); + } + + catch ( const TestFailureX& e ) + { + record.log( e ); + } + catch ( const TestErrorX& e ) + { + record.log( e ); + } + catch ( const std::exception& e ) + { + record.log( e ); + } + catch ( ... ) + { + record.log( "Unknown" ); + } + + + _ctx.tout(TestContext::Results) << record << std::endl; +} + +////////////////////////////////////////////////////////////////////////////// + +TestSuite::TestSuite( const std::string& name ) : Test( name ) +{ +} + +void TestSuite::add( Test* pTest ) +{ + _tests.push_back( pTest ); +} + +Test* TestSuite::findChild(const std::string& name) +{ + for(Tests::iterator it = _tests.begin(); + it != _tests.end(); + ++it){ + + if ((*it)->name() == name) return (*it).get(); + } + + return 0; +} + +bool TestSuite::accept( Test::Visitor& v ) +{ + if ( v.visitEnter( this ) ) + { + Tests::iterator end = _tests.end(); + for ( Tests::iterator at = _tests.begin(); at != end; ++at ) + if ( !(*at)->accept( v ) ) + break; + } + + return v.visitLeave( this ); // continue with siblings? +} + +////////////////////////////////////////////////////////////////////////////// + +bool QualifiedTestPrinter::visit( TestCase* pTest ) +{ + std::cout << currentPath() + pTest->name() << std::endl; + return true; +} + +////////////////////////////////////////////////////////////////////////////// + + +}; diff --git a/src/osg/Vec3.cpp b/src/osg/Vec3.cpp new file mode 100644 index 00000000000..6dba66e7e68 --- /dev/null +++ b/src/osg/Vec3.cpp @@ -0,0 +1,70 @@ +// Vec3 is implemented entirely in the header. This .cpp file just +// contains utx test code + +#ifdef OSG_COMPILE_UNIT_TESTS + +#include +#include + +#include + +namespace osg +{ + +class Vec3TestFixture +{ +public: + + Vec3TestFixture(); + + void testAddition(const osgUtx::TestContext& ctx); + void testSubtraction(const osgUtx::TestContext& ctx); + void testScalarMultiplication(const osgUtx::TestContext& ctx); + void testDotProduct(const osgUtx::TestContext& ctx); + +private: + + // Some convenience variables for use in the tests + Vec3 v1_, v2_, v3_; + +}; + +Vec3TestFixture::Vec3TestFixture(): + v1_(1.0f, 1.0f, 1.0f), + v2_(2.0f, 2.0f, 2.0f), + v3_(3.0f, 3.0f, 3.0f) +{ +} + +void Vec3TestFixture::testAddition(const osgUtx::TestContext&) +{ + OSGUTX_TEST_F( v1_ + v2_ == v3_ ) +} + +void Vec3TestFixture::testSubtraction(const osgUtx::TestContext&) +{ + OSGUTX_TEST_F( v3_ - v1_ == v2_ ) +} + +void Vec3TestFixture::testScalarMultiplication(const osgUtx::TestContext&) +{ + OSGUTX_TEST_F( v1_ * 3 == v3_ ) +} + +void Vec3TestFixture::testDotProduct(const osgUtx::TestContext&) +{ + +} + +OSGUTX_BEGIN_TESTSUITE(Vec3) + OSGUTX_ADD_TESTCASE(Vec3TestFixture, testAddition) + OSGUTX_ADD_TESTCASE(Vec3TestFixture, testSubtraction) + OSGUTX_ADD_TESTCASE(Vec3TestFixture, testScalarMultiplication) + OSGUTX_ADD_TESTCASE(Vec3TestFixture, testDotProduct) +OSGUTX_END_TESTSUITE + +OSGUTX_AUTOREGISTER_TESTSUITE_AT(Vec3, root.osg) + +} + +#endif