Writing a unit test for a singleton implementation

Idan Arye GenericNPC at gmail.com
Thu May 16 17:57:29 PDT 2013


On Thursday, 16 May 2013 at 21:10:52 UTC, Dmitry Olshansky wrote:
> 16-May-2013 02:21, Idan Arye пишет:
>> But
>> I *want* to (almost)cause a race condition here
>
> You can't "cause" a race condition - it's there by definition 
> of code.
> What you can cause is a data corruption that happened with one 
> bad interleave of reads/writes because it was possible.

That's why I added the "(almost)". A race condition happens when 
one thread reads a variable and writes it back based on the old 
value, and between that read and write another thread writes to 
that variable. By adding a `sleep()` between that read and write, 
I can conjure my own race condition. Ofcourse, the race condition 
will never happen, because the singleton's implementation uses 
synchronization to prevent it - but that's the whole point of my 
unit test, to check that the implementation is doing the 
synchronization correctly.

> I'm not seeing this test do anything useful anyway - you know 
> there is a race condition and then you try to test to see if it 
> works as just as if there is none.
>
> It's more like unscrewing a bunch of bolts and seeing if the 
> car manages it to the other town. It might or not depending on 
> how the driver rides it and on a ton of other chances - truth 
> be told it's blind luck almost. Even if you unscrew the same 
> bolts exactly the same way the 2nd car will not ride exactly as 
> long a distance as 1st one.

But if I have a system in my car that allows it to keep traveling 
even after unscrewing some bolts, and I want to test it, the way 
to do it is to unscrew those bolts and try to drive it to the 
other town. If I can't get to the other town, that means that 
system failed.

That's what I'm doing here - I have a system that prevents a race 
condition in the singleton's instantiation, and in order to test 
that system I try to force a race condition in the singleton's 
instantiation.

> And if your PC was compressing video or serving some HTTP 
> you'll have even less no matter how you try to run them I 
> guess...

OK, I just tested it by playing for video files at once, and the 
accuracy dropped from 100% to around 96%.

Still, the point of unit tests is to make sure that code changes 
do not break old code. Most of them are super trivial, because 
many of the bugs they prevent are also super trivial - the kind 
of bugs that make you want to hit yourself for being so stupid 
you didn't notice them before. If the library or system have a 
good set of unit tests, a programmer that runs them can catch 
those bugs happening in trivial examples right after they are 
introduced.

So, if a Phobos\druntime\dmd developer somehow manages to change 
something that breaks my singleton implementation, the next time 
they run the unit tests there is over 90% chance that unit test 
will catch the bug even if they put a heavy burden on their 
machine - and if they don't put such a heavy burden while 
developing, those chances climb very near to 100%.

So yea, my unit test is not 100% accurate in worst case scenarios 
- but it still does it's job.

> But if you like it I think Thread.yield will work just as well, 
> it will cause threads to do the context switch.

I tested both. `Thread.yield()` gives around 92% accuracy, while 
`Thread.sleep(dur!"msecs"(0))` gives 100% accuracy(when I don't 
play 4 video files at once). I have no idea why this happens, but 
it happens.

> Sleep doesn't guarantee it. It causes context switch though and 
> that might be what you want. Maybe creating a bunch of threads 
> as suspended and then waking them all up could get close to 
> that.

That's what I did in the second version - I suspended all the 
threads using `core.sync.barrier`, and the barrier took care to 
resume them all at once. This allowed me to use a 0ms sleep - but 
the call to `sleep()` is still required.

> Another problem is about expecting something definite out of 
> race condition. Yes, here you are getting away with single 
> atomic read/write of pointer simply because of x86 architecture.

How is it an atomic read/write if I call `sleep()` *between* the 
read and the write?

> Technically what you'll can see with a race condition is 
> undefined (not speaking of this simple example on x86 that IMO 
> is pointless anyway).
> Thus trying to catch it in more complex situation will require 
> more then just putting a sleep(xyz) before a function call.
>
> Imagine trying to test a lock-free collection ? You still need 
> to lauch many threads and somehow try to schedule them funky. 
> Even then I don't see how 1 single-shot can be reliable there.

True - my testing method is not possible with lock-free 
collections, since I can't put a `sleep()` call inside an atomic 
operation. But I *can* put a sleep call inside a `synchronized` 
block(or more precisely, inside a constructor that is being 
invoked inside a synchronized block), so my testing method does 
work for my case.


More information about the Digitalmars-d mailing list