|SubFork and ThreadedSUnit|
January 28, 2006, 11:32:05 pm
Once again Smalltalk shines bright with what it can do. I've been struggling to make the unit test runner for our 'Releaser' program at Software WithStyle handle the complexity of the WithStyle tests. Not only are there a lot of them, but they also spawn off subprocesses and open/close windows constantly.
This places a lot of strain on the resource management of the test runner - when people run tests they run a couple at a time. To run all of them one after the other without a breath for the GC can be cause for concern.
VisualWorks's Garbage Collector is extremely powerful but even it cannot handle loose resources. My major problem in upgrading to a new test runner was the old SUnit design flaw - the one where #tearDown is meant to nil out instance variables. What an appalling bad design decision that was. We published SUnitMemoryFix to public store which ensure that this design flaw is fixed.
But now I've gotten off topic. The second biggest problem in testing WithStyle was the UI stuff and subprocesses. There was hope from Niall Ross with his XProcSUnitPatterns which we began talking about at Smalltalk Solutions 2005 at a brief BOF.
Unfortunately, when I tried to use Niall's solution against WithStyle's tests - DeferredActions and other such nasties kept the tests from completing and caused the entire thing to lock up. After some correspondence with Niall about this I soon realised I was facing this problem on my own.
No worries - I just let it sit until this weekend I got so annoyed with constant False-Negatives from our current test runner that I just had to find an answer.
In stepped SubFork. SubFork has just been published to Public Store. It serves two purposes - you can now create 'Child' processes or 'Sub' processes. The difference is this: A Child process knows its parent and is automatically wrapped up in the same exception handlers as its parent process. A Sub process knows its parent and has a copy of its parents environment attributes.
Essentially, I'm now able to 'wait' on sub and child processes in a test runner and better yet, if the process is a child and it errors, the parent process is notified. So, I made ThreadedSUnit. This is a basic test runner which extends the current SUnit classes with #threadedRun. ThreadedRun will wait on sub processes created by running the test. It assumes your code will call #child where appropriate and will therefore catch any failures/errors that happen in those processes. ThreadedSUnit has also just been published to Public Store.
I just finished running a release of WithStyle using this new test runner - finally after many years of struggling I have a test runner that gives me accurate results and doesn't drain a machines resources or crash.
So why is this a shining achievement of Smalltalk? Well, I'll explain how #child works. Basically, whenever you wrap up a block of code in an exception handler, eg:
[self runTest] on: Failure do: [:sig | failed := true]
We want to know about it. So that if inside #runTest we do this:
[self doStuff] child
Then the new child process will also have the same exception handler. To do this, I overrode BlockClosure>>on:do: to record exception creation against the current process and I made #child 'replay' those exception handlers in the new process.
The one drawback with this approach is that you cannot have a return inside your exception handler. If the exception is raised in the subprocess you would get a 'Context cannot return' exception.
But, armed with that knowledge, I'm sure you can make good use of this new free library. Enjoy!