Practical Testing: 39 - 19 years' unit testing the same code
Back in 2004 I started a series of blog posts called “Practical Testing”, about unit testing a non-trivial piece of C++ code. The idea was to show how adding unit tests to existing, real-world, code could be useful and could support future development and refactoring. Episodes 1 though 13 were written in 2004 and covered getting the code under test and fixing some bugs that were complicated to reproduce in a live environment. From then on new posts were written as and when the functionality changed or new bugs were located.
In 2005 there were two new entries covering the fixing of a known bug and then fixing a performance problem once the code was being pushed harder than it had before.
In April 2008 some work for a client unearthed a bug which then necessitated a whole new approach to how we managed the timers, moving from the
GetTickCount() API (whose limitations had started the whole series due to code breaking when the
GetTickCount() counter rolled over to zero after a machine had been active for 49.7 days) to
GetTickCount64() (which has limits that mean a counter rollover doesn’t happen until a machine has been active for 584942417.4 years). I then removed the potential to deadlock due to lock inversions, removed duplicate code that had been added during the move to the new API and then a final new episode to fix a bug.
In July 2010 work for one of my clients necessitated performance improvements and in investigating performance and making these changes I discovered bugs in the original code and, in episode 21, was supported in fixing these by the unit tests that I had already written. The end result of this foray was a timer wheel implementation of the original interface. This was a drop in replacement with different performance characteristics and it solved the client’s performance issues nicely. Developing and testing the timer wheel was spread over episodes 23 through 28.
After the timer wheel implementation I started looking at reducing contention with custom heaps, this was, pretty much, a waste of time and effort and none of those changes are still in the codebase today.
In January 2011 the unit tests supported me in fixing a bug that had been discovered in real-world usage of the code by a client…
In 2014 I finally did what I’d been talking about back in 2010, and before, and wrote my own container classes to replace the Standard Template Library maps and whatever that I was using and that were less than ideal for the usage pattern of the timer queue. The use of invasive or intrusive containers removed the need for dynamic memory allocation and release during normal operation of the timer queue and this reduced contention for the heap and improved performance. This was the kind of change that unit tests are really useful for as the tests all enforced how the code should, and did, work and we could then safely change the “how”, knowing that the tests would spot any problems.
In 2016 some changes to other code introduce a bug into the timer queue and I spend a while writing a test that demonstrates the problem and then fixing it.
Finally in 2020, in episode 38, I brought the code up to date, removed unsupported compilers and supported a few new ones…
And now we’re here. Once again the code needs to be brought up to date. Things have moved on with the framework and we now support Linux and MacOS as well as Windows and so the code has moved around somewhat; I’ll update those aspects of the code in a later episode, this time around I’ll just get the previous code building with the latest supported compilers as we now only support Visual Studio 2017, 2019 and 2022.
The code is here on GitHub and new rules apply; The code will build with Visual Studio 2017, 2019 and 2022. Win32Tools is the solution that you want and Win32ToolsTest is the project that you should set as active. The code uses precompiled headers the right way so that you can build with precompiled headers for speed or build without them to ensure minimal code coupling. The various options are all controlled from the “Admin” project; edit
TargetWindowsVersion.h to change things… By default the project is set to build for Windows 10; this will mean that the code WILL NOT RUN on operating systems earlier than Windows Vista as it will try and use
GetTickCount64() which isn’t available.
This version of the code is in the Public Domain.
Now that the code is up to date we can continue to move forward. There are some clean ups to do; updating to the latest code that’s moving towards being cross-platform, finally renaming
CCallbackTimerQueue, which was first mooted in episode 37, back in 2016 and switching from the re-entrant lock in
CThreadedCallbackTimerQueue to a slightly more performant, non re-entrant lock now that the chance of re-entrant locking has been removed (again in episode 37). Then there’s some new functionality we can add to improve performance again.