Practical Testing: 1 - Introduction
I’m writing some blog entries that show how to write tests for non trivial pieces of code. This is part 1.
The code that we’re going to test is CCallbackTimer
. This is a class that lives in our Win32Tools library. It’s used by users of our SocketTools library. The class provides a very light-weight timer manager that gets around the various issues we had with using standard Win32 timers.
The timer is designed to be used in vast quantities. Let’s have a timer, or two, for each connection and let’s have 10,000 connections… Because of this we wanted the timer to be something that we could create lots of without caring.
The threading rules and potential ‘weight’ of WaitableTimers meant that they weren’t of use to us.
The fact that we needed to support NT 4 and Windows 95/98 meant that we couldn’t use TimerQueues, which is a pity because they’re otherwise a pretty good match.
The class implements a timer queue. Timers are placed on the queue and the class deals with making sure they’re kept in the correct order (soonest first). A thread sleeps for as long as is necessary for the first timer and then wakes up and calls a user supplied callback function. The thread then goes back to sleep until the next timer expires. You can cancel and reset timers before they go off and you can pass user data to the callback function.
The code is less than ideal. It was written during a fairly high pressure phase of a project and that shows. I feel it’s a bit too complicated for its own good and there’s been a known bug in the code since the start.
The current complexity and the bug is why we’re interested in writing tests. We want a test that proves the existence of the bug so that we can fix the bug and make the test pass. We’d like to have additional tests in place before we do that so that we can make sure that we don’t break any of the existing functionality when we fix the bug.
The class is reasonably cohesive (it does just one thing) and at first sight doesn’t appear to be too highly coupled to other concepts; it only uses basic types and its own handle type in its function signatures. However when we tried to break the code out into the smallest project that would compile we discovered that it uses a surprising number of other classes to get its work done.
The Win32Tools library is generally where we keep code that makes Win32 primitives a little easier to use; we have events, threads, critical sections, a wide/narrow string that is the correct width depending on your UNICODE
settings, etc. The callback timer uses a thread object because it runs in its own thread, it uses a critical section to allow correctly syncronised access to its data structures, an auto reset event so that the thread can wait for the next timeout and yet still be controllable if we want to shut down the timer queue. The code that the callback timer object uses directly also uses other code; most things throw one of our standard exception types and most things use _tstring
for strings. This isn’t too bad really since all of these things live in the same library and the user of CCallbackTimer
doesn’t really have to understand many of them to use CCallbackTimer
.
The class also uses another library; our C++Tools library. This was originally seen as a place to keep stuff that isn’t platform specific and yet isn’t specific to any particular business need. We found there wasn’t that much that fell into this category; or at least we’ve been less aggressive at refactoring and harvesting this kind of code. The callback timer object uses CNodeList
from this library. CNodeList
is our home grown invasive doubly linked list. It’s used in the callback timer because when we want to cancel or reset a timer we have direct access to the node in the list and thus removals are simple. I’d really like to be rid of this dependency…
So, there we have it. The code that we’re intending to test can be downloaded from here. I’m presenting them in ‘lowest common denominator’ format, i.e. VC 6 projects, since I can’t be bothered to maintain 3 project file formats and all of the later tools will load and convert the earlier projects. Load the Win32Tools.dsw workspace and you’re away.
Now on to the bug: The callback timer uses GetTickCount()
to work out when now is and when its timers need to go off. It uses absolute times in milliseconds from the GetTickCount()
time continuum. It does this because when a timer goes off it needs to work out how long it needs to sleep for it and simply say the next timer is due in (absolute time of next timer - now) milliseconds
. Unfortunately GetTickCount()
wraps from a big number to 0 every 49.7 days and the code we have ignores this fact; assuming that ’later’ is always bigger. The code is broken in several places, the breaks are reasonably easy to fix, but proving that the code is broken and writing a test to expose the break is non trivial.
In the next posting we’ll write the first test.