Testing & Debugging Miscellanea

Here’s a quick peek into some of the test and debug strategae I’m using…

Base Object

One of the great hazards of C++ is object leaks. To mitigate this, I have a base object that everyone descends from, called MeObjectBase. My descendant object constructors all look like

MeRenderWorld::MeRenderWorld(int width, int height) : MeObjectBase("MeRenderWorld")
{ ... }

Then we can print a tally of current objects by type name. But all that text-dictionary lookup can be expensive! So there’s a global boolean to enable or disable count-by-name. Even when disabled, the total number of objects is tracked.

Malloc

Like object leaks, memory leaks are alway looming. The main code never calls malloc or free directly. Instead, some wrappers are used which keep a tally of how much memory has been allocated and freed, and how many pointers have been allocated and freed. A few bytes at the beginning of the block hold the size. These static methods on MeObjectBase.

class MeObjectBase
{
public:
      ...    
    static void *malloc(size_t bytes);
    static void reallocAt(void **ptr, size_t bytes);
    static void freeAt(void **ptr);
};

Unit Testing

I’ve been coding since I was 12. That’s forty years now, but who’s counting. The only significant thing I’ve learned in the last twenty or so is the joy and beauty of organized unit tests. I used to do unit tests, without realizing it: I’d write some code at the top of main() to call the new function, print out the result, and quit. Then I’d delete the test and continue development.

I’ve also often written little test apps alongside my “real app” to exercise libraries.

Anyway, yeah, unit testing. For Metareal, I’m just running a small command line app that tallies up trues and falses and prints the result at the end.

Why not use an existing C++ unit testing framework? No great reasons, but among them: Tends to run slower as files are scanned or preprocessed for tests, adds code I don’t know that well. On the other hand, testing frameworks typically let you run single tests if needed. And I have to explicitly add every test function to main(). Oh well, is a tradeoff.

Here’s what some tests look like:

// A typical "main method" for a test file
void allMatrixTests()
{
    castingFloatToVec4();
    vec4ToVec3Tests();
    matrixRowColumnExtracts();
    matrixTests();
    matrixTestWTranslate();
    matrixTestLTranslate();
    vectorCallTest();
    basicMatrixMathFun();
}

// A typical assertion
    float m03 = m4(0,3);
    ASSERT_EQUALS_FLOAT("col/row", 3, m03);

// Implementation of one of the assertions
#define ASSERT_EQUALS_INT(_message,_want,_got) assertEqualsInt(1,__LINE__,_message,#_want,#_got,(long long)_want,(long long)_got)
void assertEqualsInt(int sayIt,int line,cc message,cc wantS,cc gotS,long long want,long long got)
{
    g.assertions++;
    if(got == want)
    {
        g.passes++;
    }
    else
    {
        g.fails++;
        assertLogFail("%6d. FAIL %4d %s %s(%lld) != %s(%d)\n",g.fails,line,message,wantS,want,gotS,got);
    }
}

The last assertion I make is that all the memory and objects have been freed. Here’s a happy output.


    currentCount:   0
constructorCount:3801
       copyCount:   0
       everCount:3801
     mallocCount:6249
       freeCount:6249
    unfreedCount:   0
     mallocBytes:2363449281
      freedBytes:2363449281
    unfreedBytes:   0
         BitmapThing:   0    2    3
                Ham1:   0    1    1
             IfThing:   0    2    6
            IxMover2:   0    2    8
   MeCollisionVolume:   0   10  134
              MeGaud:   0    3   34
          MeGeometry:   0 1001 1541
               MeHam:   0    1    1
          MeITexture:   0    4   81
               MeLru:   0    2    3
              MePart:   0 1000 1190
       MeRenderParts:   0    3   59
       MeRenderWorld:   0    1   19
      MeTextureAtlas:   0    2   23
     MeTextureFloat4:   0    3   22
         MeThingKind:   0    1    8
      MeThingManager:   0    1    4
            MeVolume:   0  128  493
       MeVolumeWorld:   0    1   25
     MockFrameBuffer:   0    2   10
        MockMaterial:   0    3   67
        MockRenderer:   0    3   28
                   a:   0    3    3
                   b:   0    1    1
             unknown:   0    3   37
     0.  ok       undisposed objects 0(0) == MeObjectBase::currentCount(0)
     0.  ok       undisposed mallocs 0(0) == MeObjectBase::mallocCount - MeObjectBase::freeCount(0)
test results: 7551 pass / 7551 assertions (0.686 seconds)
---------------------
 aok
---------------------
Program ended with exit code: 0

Thing to notice: the total runtime for these tests is about a second. It covers the math, part and triangle-list management, many, many collision and volume intersection cases. It does not cover actual, live OpenGL code.

Debug Logging

Little to say here, I have a couple of log methods, which take arguments like printf. Listeners can be added.

Debug Global Booleans

Aaah, yes, debugging realtime code can be tricky. To help, when running the realtime app I map control-0 through control-9 directly to ten globally accessible booleans. Sometimes I’ll add some code to a deep, inner function which does something special based on those booleans. Then I can trigger it at will while running. Works nicely with breakpoints.

Well, Ok

That’s just some of the goodies in play.

Leave a Reply

Your email address will not be published.