| 1 |
_Introduction_ |
| 2 |
|
| 3 |
It's very important that you put debugging statements into your code - |
| 4 |
even if your code seems to work now, it can fail in future and someone |
| 5 |
else will have to come along and try to work out what's going wrong. |
| 6 |
|
| 7 |
Just because a program compiles doesn't mean that it works perfectly - |
| 8 |
in fact, C++ compilers don't even check that functions compile |
| 9 |
completely unless the function is called somewhere in a program. If |
| 10 |
at all possible, you should write a complete test suite for your part |
| 11 |
of Globulation 2 and put it with the others in testing/. That makes |
| 12 |
sure your code compiles, runs, and gives correct results, but it also |
| 13 |
keeps others from getting into too much trouble when they come along |
| 14 |
later and try fiddle with your code because they think they can do it |
| 15 |
better. |
| 16 |
|
| 17 |
The key to writing a good test suite is to make sure every line of |
| 18 |
code you've written is run at least once, and that a good selection of |
| 19 |
interesting inputs (such as minimum and maximum allowed values) |
| 20 |
produce the outputs you expect. |
| 21 |
|
| 22 |
Even if you only debug your code in an ad-hoc way instead of writing a |
| 23 |
complete test suite, you can do a lot to make your life easier when |
| 24 |
you come to maintain it in future. It's very tempting to write tests |
| 25 |
and reports into your code during debugging, then remove or comment |
| 26 |
them out when you're done - Globulation 2's debugging system is |
| 27 |
designed so that you can disable them as a block, then re-enable them |
| 28 |
all again when it comes time to maintain the code. |
| 29 |
|
| 30 |
_Background_ |
| 31 |
|
| 32 |
Glob2 allows the user to set a debug level at run-time, where |
| 33 |
increasing the debug level causes the program to do more tests and |
| 34 |
print more verbose output. The interface for the user hasn't been |
| 35 |
written yet, but will probably be a command-line option. |
| 36 |
|
| 37 |
The debugging interface for programmers is based on a template class |
| 38 |
Debug<logLevel, testLevel>. Tests and messages will be ignored unless |
| 39 |
the user-specified debug level is greater than or equal to the |
| 40 |
template parameter. For example, if the debug level is set to 8 and |
| 41 |
you use the class Debug<7, 9> in your code, your messages will be |
| 42 |
displayed to the user (because 8 >= 7), but tests will be ignored |
| 43 |
(because 8 < 9). |
| 44 |
|
| 45 |
You should normally set the template parameters for the Debug<> class |
| 46 |
in the range 0..127. Messages and tests with level 127 are guaranteed |
| 47 |
to be ignored, messages with level 0 are guaranteed to be displayed, |
| 48 |
|
| 49 |
Tests with level 0 will only be disabled in stable releases of the |
| 50 |
game, messages with level 0 are guaranteed to be displayed, tests and |
| 51 |
messages with level 127 are ignored and completely compiled out of the |
| 52 |
program, anything in between depends on the current debugging level. |
| 53 |
Negative levels are treated like level 0, so you can temporarily |
| 54 |
enable a set of tests by putting a minus sign in front of the levels |
| 55 |
in the associated class. |
| 56 |
|
| 57 |
The user will probably be presented with debug levels in the range |
| 58 |
0..9 or 0..99, which will have to be mapped across - so, for example, |
| 59 |
0->0, 1->14, 2->28, and so on up to 9->126, with 127 reserved for |
| 60 |
completely disabled tests. |
| 61 |
|
| 62 |
_Interface_ |
| 63 |
|
| 64 |
Debugging is done with the Debug<logLevel, testLevel> class. The |
| 65 |
previous section explains these levels in detail, but basically the |
| 66 |
higher the level, the less likely your test is to be performed. The |
| 67 |
template parameters can be left with their default values - Debug<> |
| 68 |
will typically be set to do more or less debugging depending on |
| 69 |
whether the code is currently in CVS, alpha, or stable release. |
| 70 |
|
| 71 |
The class contains the following static member functions: |
| 72 |
|
| 73 |
* invariant(bool) |
| 74 |
An invariant is a test that should always be true. This |
| 75 |
function returns false if debugging is enabled for the current |
| 76 |
class and the specified test is false. |
| 77 |
|
| 78 |
* info(DEBUG_INFO) |
| 79 |
* info() |
| 80 |
This function returns an output stream (or at least something |
| 81 |
that you can "<<" to), which sends your output to the standard |
| 82 |
error, a log file, message box, bit bucket, or wherever the |
| 83 |
program decides is appropriate. The argument "DEBUG_INFO" is |
| 84 |
a macro that expands to information about the current file, |
| 85 |
line, and function. The exact values depend on the compiler |
| 86 |
you're using, and will probably be printed out along with the |
| 87 |
message. Obviously, the version of the function with no |
| 88 |
argument will not print this information. |
| 89 |
|
| 90 |
* error(DEBUG_INFO) |
| 91 |
* error() |
| 92 |
Like info(), but is also guaranteed to inform the user that |
| 93 |
something bad has happened. |
| 94 |
|
| 95 |
* terminate(ErrorClass) |
| 96 |
* terminate() |
| 97 |
Throw ErrorClass (or std::exception if no class is specified), |
| 98 |
informing the user if possible. |
| 99 |
|
| 100 |
_Examples_ |
| 101 |
|
| 102 |
Typically, you should create a typedef like this near the start of |
| 103 |
your class: |
| 104 |
|
| 105 |
typedef Debug<7, 9> debug; |
| 106 |
|
| 107 |
While writing a new class, you might prefer to use two debugging |
| 108 |
typedefs: |
| 109 |
|
| 110 |
typedef Debug<0, 0> dbg; |
| 111 |
typedef Debug<> debug; |
| 112 |
|
| 113 |
Then you can enable all debugging and checking for a new function as |
| 114 |
you write it, and control all your other functions as a group by |
| 115 |
editing the general debug class. |
| 116 |
|
| 117 |
Tests are typicall defined like this: |
| 118 |
|
| 119 |
if (!debug::invariant(myValue <= maxValue)) |
| 120 |
debug::error(DEBUG_INFO) |
| 121 |
<< "\tmyValue=" << myValue << '\n' |
| 122 |
<< debug::terminate(); |
| 123 |
|
| 124 |
If an invariant fails to hold, this will print a nicely formatted |
| 125 |
message and exit. If you don't need to do a separate test, you could |
| 126 |
use a system like this: |
| 127 |
|
| 128 |
switch (myCase) |
| 129 |
{ |
| 130 |
case 1: // handle case 1 |
| 131 |
case 2: // handle case 2 |
| 132 |
default: // error: can't happen |
| 133 |
debug::terminate(); |
| 134 |
}; |
| 135 |
|
| 136 |
Which will just kill the program outright. |