Ship-It Saturday: PRHEmptySingleton repository

2010-09-04 09:06:05 -08:00

The singleton-done-right example from my article on the subject is now in a Mercurial repository on Bitbucket. The repository includes not only the class (which I’ve put in the public domain), but also a test suite for some of the test cases listed in the post.

There are some adventurous testing techniques at work here.

First, since we don’t want multiple test runs to use the same singleton instance, the test cases actually run in subprocesses. Each test method is prefaced with this code:

if (!isInSubprocess) {
    [self runTestInSubprocess:_cmd];

That method calls fork.

In the child process, the test case sets isInSubprocess to YES, then tells itself to perform that selector; this gets execution back to the test method, which checks the variable again, finds that it’s true this time, and continues on with the test.

The parent process calls waitpid and checks the result; if the child failed, the parent propagates the failure. If the child crashed (got a signal), the parent raises the same signal; if the child simply exited abnormally, then the parent exits with the same status.

Second, there’s test case #6:

  • If [super init] returns a different object, alloc/init won’t break.

That one is hard to test, because PRHEmptySingleton’s superclass is NSObject, and -[NSObject init] doesn’t return a different object. (Not in any version of Foundation I’ve ever encountered, anyway.)

So, the first step is to create an intermediate class that does return a different object. But that doesn’t help as long as PRHEmptySingleton is still a direct subclass of NSObject.

The simple solution would be to just change PRHEmptySingleton’s superclass, but that reduces the purity of the testing: A test should be able to work without modifying the code under test, and any changes to the code under test should be permanent changes that aren’t only to enable the test; you should be able to explain the changes as if the test case did not exist.

So what I did was to import everything in my prefix header, even more than I usually do, and then import my intermediate class’s header, and then use the preprocessor to ensure that any direct subclasses of NSObject declared elsewhere are declared as subclasses of the intermediate class. Thus, the prefix header causes PRHEmptySingleton to be a subclass of the intermediate class with no changes to PRHEmptySingleton’s header. It’s a bit of a hack, and doing this sort of thing could potentially cause problems in a larger program or test suite, but in this context, it works.

With that, five of the six test cases in the original post are now covered (I’m not sure how to cover #3 without changing PRHEmptySingleton.[hm]), and the code is under version control, so you can subscribe to and track changes.

Please use singletons responsibly.

4 Responses to “Ship-It Saturday: PRHEmptySingleton repository”

  1. Jesper Says:

    It seems funny to make unit tests for singletons, since singletons themselves reduce mockability.

  2. Jonathan Wight Says:

    I’m still hating on your singleton technique. ;-)

  3. Ken Says:

    A testing involving Cocoa (or any Objective-C messaging) after a fork() without an exec is invalid. This is mentioned in a couple of places by Apple, but the strongest wording is no longer online. It is in the Leopard CoreFoundation release notes, which can be accessed if you install the Leopard docset in Xcode. I quote it below. I add the note that objc_msgSend uses non-async-cancel-safe functions and is thus similarly unsafe. (For example, it could call malloc, but the malloc lock was held by a thread which was unceremoniously eliminated by the fork call.)

    Quoting Apple’s docs:

    CoreFoundation and fork()

    Due to the behavior of fork(), CoreFoundation cannot be used on the child-side of fork(). If you fork(), you must follow that with an exec*() call of some sort, and you should not use CoreFoundation APIs within the child, before the exec*(). The applies to all higher-level APIs which use CoreFoundation, and since you cannot know what those higher-level APIs are doing, and whether they are using CoreFoundation APIs, you should not use any higher-level APIs either. This includes use of the daemon() function.

    Additionally, per POSIX, only async-cancel-safe functions are safe to use on the child side of fork(), so even use of lower-level libSystem/BSD/UNIX APIs should be kept to a minimum, and ideally to only async-cancel-safe functions.

    This has always been true, and there have been notes made of this on various Cocoa developer mailling lists in the past. But CoreFoundation is taking some stronger measures now to “enforce” this limitation, so we thought it would be worthwhile to add a release note to call this out as well. A message is written to stderr when something uses API which is definitely known not to be safe in CoreFoundation after fork(). If file descriptor 2 has been closed, however, you will get no message or notice, which is too bad. We tried to make processes terminate in a very recognizable way, and did for a while and that was very handy, but backwards binary compatibility prevented us from doing so.

  4. Peter Hosey Says:

    The wording is actually even stronger now than anything in the documentation: Attempting to call CF APIs after forking is now a good way to get an exception.

    So far, the tests are getting away with it (presumably not currently reaching a CF API). If that ever changes, it will just have to be acknowledged, since there’s no way to fix it.

    When I plugged Apple’s example into my tests, it mostly worked fine: It only failed one of the tests, and it was the least important one (over-releasing must crash). Turns out Apple updated the code late last year, so it’s much better now. If I were to run across that example today, I would not write PRHEmptySingleton. So, if you have doubts about my example, use Apple’s example—their revised version is fine.

Leave a Reply

Do not delete the second sentence.