root/OpenSceneGraph/trunk/examples/osgunittests/UnitTestFramework.h @ 6941

Revision 6941, 15.8 kB (checked in by robert, 7 years ago)

From Martin Lavery and Robert Osfield, Updated examples to use a variation of the MIT License

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/* -*-c++-*-
2*
3*  OpenSceneGraph example, osgunittests.
4*
5*  Permission is hereby granted, free of charge, to any person obtaining a copy
6*  of this software and associated documentation files (the "Software"), to deal
7*  in the Software without restriction, including without limitation the rights
8*  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9*  copies of the Software, and to permit persons to whom the Software is
10*  furnished to do so, subject to the following conditions:
11*
12*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13*  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14*  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15*  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16*  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17*  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
18*  THE SOFTWARE.
19*/
20
21#ifndef OSG_UNITTESTFRAMEWORK
22#define OSG_UNITTESTFRAMEWORK 1
23
24#include <osg/Export>
25#include <osg/Referenced>
26#include <osg/ref_ptr>
27#include <osg/Timer>
28#include <osg/Notify>
29
30#include <string>
31#include <vector>
32#include <list>
33#include <fstream>
34
35/**
36
37\namespace osgUtx
38
39The osgUtx is a unit test framework.
40*/
41
42namespace osgUtx{
43
44class TestVisitor;
45
46
47/**
48Test, an abstract base class, is the Composite pattern's \em component
49class for our graph of test cases, and defines the basic interface
50for all Test components. It is a referent, and may be pointed
51to by an osg::ref_ptr.
52*/
53class Test: public osg::Referenced
54{
55    public:
56
57    typedef TestVisitor Visitor;    // Test is redundant
58
59    Test( const std::string& sName ) : _name( sName ) {}
60
61    const std::string& name() const { return _name; }
62
63    virtual bool accept( Visitor& ) = 0;
64
65    protected:
66
67        virtual ~Test() {}
68
69    std::string _name;
70};
71
72
73/**
74TestContext wraps up information which is passed to tests as they are run,
75and may contain test-specific information or 'global' test objects, such
76as an output stream for verbose output during the running of tests.
77
78\todo Improve the output stream code by providing a filtering stream.
79*/
80class TestContext
81{
82public:
83
84    TestContext();
85
86    bool shouldStop()    { return false; }
87    bool isVerbose()    { return true; }
88
89    enum TraceLevel{
90        Off,        ///< All tracing turned off
91        Results,    ///< Output results only
92        Full        ///< Full test diagnostic output
93    };
94
95    void setTraceLevel(TraceLevel tl);
96    TraceLevel getTraceLevel() const;
97
98    std::ostream& tout(TraceLevel tl=Full) const;
99
100private:
101
102    TestContext(const TestContext&);
103    TestContext operator=(const TestContext&);
104
105#ifndef DOXYGEN_SHOULD_SKIP_THIS
106
107    class TraceStream{
108
109    public:
110        TraceStream(std::ostream& o=osg::notify(osg::NOTICE), TraceLevel tl=Results);
111        ~TraceStream();
112
113        void setTraceLevel(TraceLevel tl);
114        TraceLevel getTraceLevel() const;
115
116        std::ostream& stream(TraceLevel tl);
117
118    private:
119
120        TraceLevel    _traceLevel;
121        std::ostream*    _outputStreamPtr;
122        std::ofstream    _nullStream;
123    };
124
125#endif /* DOXYGEN_SHOULD_SKIP_THIS */
126
127    mutable TraceStream _tout;
128
129};
130
131
132class TestSuite;
133class TestCase;
134
135/**
136Visits while maintaining the current hierarchical context. Also allows
137the traversal to be short-circuited at any point during the visitation.
138*/
139class TestVisitor
140{
141    public:
142
143    //..Should we enter this node and its children?
144    virtual bool visitEnter( TestSuite* ) { return true; }
145
146    //..Returns true to continue to next Leaf
147    virtual bool visit( TestCase* ) = 0;
148
149    //..Returns true to continue to next Composite
150    virtual bool visitLeave( TestSuite* ) { return true; }
151       
152    protected:
153
154    TestVisitor() {}
155    TestVisitor( const TestVisitor& ) {}
156    virtual ~TestVisitor()    {}
157};
158
159/**
160TestCase, supplies the interface for a Composite pattern's
161\em leaf class, though it is not a leaf in itself.
162*/
163class TestCase : public Test
164{
165    public:
166
167    typedef TestContext Context; // Test in TestContext? is redundant
168
169    TestCase( const std::string& sName ) : Test( sName ) {}
170
171    virtual bool accept( Visitor& v ) { return v.visit( this ); }
172
173    virtual void run( const Context& ) = 0;  // Subclass OSGUTX_EXPORT Responsibility
174
175    protected:
176
177        virtual ~TestCase() {}
178};
179
180/**
181Base class catchable for the exceptions which may be thrown to
182indicate problems during the run of a TestCase.
183*/
184class TestX
185{
186    public:
187
188    TestX(const std::string& s):_what(s)    {}
189    virtual ~TestX() {}
190
191    const std::string& what() const { return _what; }
192
193    private:
194    std::string    _what;
195};
196
197/**
198A TestFailureX indicates a failure in the tested component.
199*/
200class TestFailureX: public TestX
201{
202    public:
203    TestFailureX(const std::string& s):TestX(s)    {}
204};
205
206/**
207A TestErrorX indicates an error while testing a component,
208which prevents the test from being run. It does not indicate
209a problem with the component, but rather a problem during the
210run which prevents the component from being tested.
211*/
212class TestErrorX: public TestX
213{
214    public:
215    TestErrorX(const std::string& s):TestX(s)    {}
216};
217
218/**
219TestCase_ is a class template for a leaf TestCase, which allows TestFixture
220classes to be easily collected into the tree of tests, and have their public
221test methods called. It is worth noting that, for a given TestCase_, an
222instance of the test fixture class will be constructed prior to the
223test method being called, and destructed afterwards. This prevents 'leakage'
224of information from one test case to the next.
225*/
226template< typename FixtureT >
227class TestCase_ : public TestCase
228{
229    typedef void (FixtureT::*TestMethodPtr)( const Context& );
230
231    public:
232
233    // Constructor adds the TestMethod pointer
234    TestCase_( const std::string& sName, TestMethodPtr pTestMethod ) :
235            TestCase( sName ),
236            _pTestMethod( pTestMethod )
237    {
238    }
239
240    // Create a TestFixture instance and invoke TestMethod?
241    virtual void run( const Context& ctx )
242    {
243        ( FixtureT().*_pTestMethod )( ctx );
244    }
245
246    protected:
247
248        virtual ~TestCase_() {}
249
250    TestMethodPtr _pTestMethod;
251};
252
253/**
254A TestSuite is the \em composite component of the Composite pattern,
255and allows aggregation of Tests into hierarchies.
256*/
257class TestSuite : public Test
258{
259    public:
260
261    TestSuite( const std::string& name );
262
263    /** Adds a Test to the suite. */
264    void add( Test* pTest );
265
266    /**
267    @returns    The immediate child denoted by name, or 0 if not found.
268    */
269    Test* findChild(const std::string& name);
270
271    virtual bool accept( Test::Visitor& v );
272
273    protected:
274
275        virtual ~TestSuite() {}
276
277    typedef std::vector< osg::ref_ptr<Test> > Tests;
278    Tests _tests;  // Collection of Suites and/or Cases
279};
280
281/**
282TestGraph is a singleton providing central access to the tree of tests;
283primarily, it provides access to the root suite.
284*/
285class TestGraph
286{
287
288    public:
289
290    static TestGraph& instance();
291
292    /**
293        @return a pointer to the root TestSuite.
294    */
295    TestSuite* root();
296
297    /**
298        A utility function for accessing an arbitrary suite by pathname, relative to
299        the suite 'tsuite' (defaults to root if null), and with the option of creating
300        the \em TestSuite designated by \em path, if it does not already exist.
301
302        This method may return 0 if the suite either cannot be found (and createIfNecssary
303        is 0), or the first component of \em path is not the same as the name of the
304        TestSuite \em tsuite.
305
306        This was written to aid the auto-registration of tests at specific points in
307        the test tree, where the tests' AutoRegistrationAgents may be distributed across
308        several files, and cannot be guaranteed to run in a given order. E.g. You cannot
309        register a test "root.osg.MyTest" unless you know that the the suite "root.osg"
310        already exists.
311       
312
313        @param path                    The name of the TestSuite to return.
314        @param tsuite                The suite to 'start from'. Path is relative to this
315                                    suite (defaults to root suite).
316        @param createIfNecessary    Optionally create the TestSuite(s) denoted by path if
317                                    they do not exist.
318    */
319    TestSuite* suite(const std::string& path, TestSuite* tsuite = 0,bool createIfNecessary = false);
320
321    private:
322
323    /**
324        Does the same job as the version of suite listed above, but the path
325        is passed in as components in a list, represented by the iterator parameters.
326    */
327    TestSuite* suite(
328        std::list<std::string>::iterator it,
329        std::list<std::string>::iterator end,
330        TestSuite* tsuite, bool createIfNecessary);
331
332    TestGraph();
333
334    TestGraph(const TestGraph&);
335    TestGraph& operator=(const TestGraph&);
336
337    osg::ref_ptr<TestSuite>    root_;
338
339};
340
341
342/**
343Maintains a string that when accessed in the "visit" member, returns the
344current qualified TestSuite path.
345*/
346class TestQualifier : public TestVisitor
347{
348    enum { SEPCHAR = '.' };
349
350    public:
351
352    // Entering a composite: Push its name on the Path
353    virtual bool visitEnter( TestSuite* pSuite );
354
355    // Leaving a composite: Pop its name from the Path
356    virtual bool visitLeave( TestSuite* pSuite );
357
358    // Provide read-only access to the current qualifier
359    const std::string& currentPath() const;
360
361    private:
362
363    std::string _path;    // Current qualifier
364};
365
366/**
367QualifiedTestPrinter prints to standard output a list of fully
368qualified tests.
369*/
370class QualifiedTestPrinter : public TestQualifier
371{
372public:
373
374
375    virtual bool visit( TestCase* pTest );
376};
377
378/**
379A TestRecord records the output of a given test case, i.e. its start/stop time,
380its result, and a textual description of any problems.
381
382\todo    Consider adding accessor methods if necessary, to get the details
383        stored in the TestRecord.
384*/
385class TestRecord
386{
387    public:
388
389        void start();
390        void stop();
391        void log(const TestFailureX& e);
392        void log(const TestErrorX& e);
393        void log(const std::exception& e);
394        void log(const std::string& s);
395
396        // Default copy construction and assignment are OK
397
398        // FIXME: Add accessors?
399
400    private:
401
402        // Onlye a TestReport can create a TestRecord
403        friend class TestReport;
404        TestRecord(const std::string& name);
405
406        enum Result{
407            Success,Failure,Error
408        };
409
410        friend std::ostream& operator<<(std::ostream& o,const TestRecord& tr);
411
412        static osg::Timer    timer_;    // To time tests
413
414        std::string        name_;
415        osg::Timer_t    start_;
416        osg::Timer_t    stop_;
417        Result            result_;
418        std::string        problem_;
419
420};
421
422/**
423A TestReport represents the complete set of results (TestRecords) for a
424given test run.
425
426\todo    Add support for printing the test report in various formats:
427        e.g. text, XML, CSV
428*/
429class TestReport
430{
431public:
432
433    TestRecord&    createRecord(const std::string& s){
434        _records.push_back(TestRecord(s));
435        return _records.back();
436    }
437
438private:
439    std::list<TestRecord>    _records;
440
441};
442
443
444
445
446
447
448
449/**
450A TestRunner is a visitor which will run specified tests as it traverses the
451test graph.
452
453\todo    Consider an accessor method to get at the TestReport if necessary.
454*/
455class TestRunner : public TestQualifier
456{
457public:
458
459    TestRunner( TestContext& ctx );
460
461    /**
462        Tests may be specified by partial names. E.g. specifiying "root"
463        will run all tests below root, i.e. all tests.
464        Specifiying    "root.osg" will run all tests below \em root.osg.
465        Specifying "root.osg.de" will run all tests (and suites) below
466        \em root.osg with names beginning with the \em de.
467    */
468    void specify( const std::string& sQualifiedName );
469
470    bool visitEnter( TestSuite* pSuite );
471    bool visit( TestCase* pTest );
472    bool visitLeave( TestSuite* pSuite );
473
474
475protected:
476
477    void perform( TestCase* pTest );
478
479private:
480
481    TestReport                   _db;            // Results
482    TestContext&                 _ctx;            // The Global Testing Context
483    std::vector<std::string>    _tests;          // Specified Tests
484};
485
486}
487
488/**
489Starts a TestSuite singleton function
490@see OSGUTX_ADD_TESTCASE, OSGUTX_END_TESTSUITE
491*/
492#define OSGUTX_BEGIN_TESTSUITE( tsuite ) \
493    osgUtx::TestSuite* tsuite##_TestSuite() \
494    { \
495        static osg::ref_ptr<osgUtx::TestSuite> s_suite = 0; \
496        if ( s_suite == 0 ) { \
497            s_suite = new osgUtx::TestSuite( #tsuite );
498
499
500
501/**
502Adds a test case to a suite object being created in a TestSuite singleton function.
503@see OSGUTX_BEGIN_TESTSUITE, OSGUTX_END_TESTSUITE
504*/
505#define OSGUTX_ADD_TESTCASE( tfixture, tmethod ) \
506            s_suite->add( new osgUtx::TestCase_<tfixture>(  \
507                                #tmethod, &tfixture::tmethod ) );
508
509/**
510Ends a TestSuite singleton function
511@see OSGUTX_BEGIN_TESTSUITE, OSGUTX_ADD_TESTCASE
512*/
513#define OSGUTX_END_TESTSUITE \
514        } \
515        return s_suite.get(); \
516    }
517
518/** Define a TestSuite accessor */
519#define OSGUTX_TESTSUITE( tsuite ) \
520    tsuite##_TestSuite()
521
522
523/**
524Adds a suite to a suite - allows composition of test suites.
525@see OSGUTX_BEGIN_TESTSUITE, OSGUTX_END_TESTSUITE
526*/
527#define OSGUTX_ADD_TESTSUITE( childSuite ) \
528    s_suite->add( childSuite##_TestSuite() );
529
530
531/** Autoregister a testsuite with the root suite at startup */
532#define OSGUTX_AUTOREGISTER_TESTSUITE( tsuite ) \
533    static osgUtx::TestSuiteAutoRegistrationAgent tsuite##_autoRegistrationObj__( tsuite##_TestSuite() );
534
535/** Auto register a testsuite with at designated point in the suite graph at startup */
536#define OSGUTX_AUTOREGISTER_TESTSUITE_AT( tsuite , path ) \
537    static osgUtx::TestSuiteAutoRegistrationAgent tsuite##_autoRegistrationObj__( tsuite##_TestSuite(), #path );
538
539namespace osgUtx{
540
541/**
542A helper struct to perform automatic registration at program startup; not for
543direct use, it should be used via the following macros. (It's a secret agent :-)
544
545@see OSGUTX_AUTOREGISTER_TESTSUITE, OSGUTX_AUTOREGISTER_TESTSUITE_AT
546*/
547struct TestSuiteAutoRegistrationAgent
548{
549    TestSuiteAutoRegistrationAgent(TestSuite* tsuite, const char* path = 0)
550    {
551        if( ! path ) path = "root";
552
553        // Find the suite named in 'path', create it if necessary
554        TestSuite *regSuite = osgUtx::TestGraph::instance().suite( path, 0, true );
555
556        if(!regSuite){
557            osg::notify(osg::WARN)<<"Warning, unable to register test suite named \""<<tsuite->name()<<"\" at "
558                              <<path<<", falling back to root suite."<<std::endl;
559            regSuite = osgUtx::TestGraph::instance().root();
560        }
561
562        regSuite->add(tsuite);
563    }
564};
565
566}
567
568/**
569OSGUTX_TEST_F is a convenience macro, analogous to assert(), which will
570throw an osgUtx::TestFailureX if \em expr evaluates to false; this should be
571used to test for failure in a given test, as opposed to an actual error
572in the test owing to some other reason than the tested code being faulty.
573
574The exception will indicate the file and line number of the failed expression,
575along with expression itself.
576*/
577#define OSGUTX_TEST_F( expr ) \
578    if( !(expr) ){ \
579        std::stringstream ss; \
580        ss<< #expr <<" failure: "<<__FILE__<<", line "<<__LINE__<<std::ends; \
581        throw osgUtx::TestFailureX(ss.str()); \
582    }
583
584/**
585OSGUTX_TEST_E is a convenience macro, analogous to assert(), which will
586throw an osgUtx::TestErrorX if \em expr evaluates to false; this should be
587used to test for an error in a given test, as opposed to a failure
588in the tested code.
589
590The exception will indicate the file and line number of the failed expression,
591along with expression itself.
592*/
593#define OSGUTX_TEST_E( expr ) \
594    if( !(expr) ){ \
595        std::stringstream ss; \
596        ss<< #expr <<" error: "<<__FILE__<<", line "<<__LINE__<<std::ends; \
597        throw osgUtx::TestErrorX(ss.str()); \
598    }
599
600
601#endif // OSG_UNITTESTFRAMEWORK
Note: See TracBrowser for help on using the browser.