views:

468

answers:

6

The classical unit testing is basically just putting x in and expecting y out, and automating that process. So it's good for testing anything that doesn't involve time. But then, most of the nontrivial bugs I've come across have had something to do with timing. Threads corrupt each others' data, or cause deadlocks. Nondeterministic behavior happens – in one run out of million. Hard stuff.

Is there anything useful out there for "unit testing" parts of multithreaded, concurrent systems? How do such tests work? Isn't it necessary to run the subject of such test for a long time and vary the environment in some clever manner, to become reasonably confident that it works correctly?

+3  A: 

I have never heard of anything that can.

I guess if someone was to design one, it would have to have exact control over the execution of the threads and execute all possible combinations of stepping of the threads.

Sounds like a major task, not to mention the mathematical combinations for non-trivial sized threads when there are a handful or more of them...

Although, a quick search of stackoverflow... http://stackoverflow.com/questions/111676/unit-testing-a-multithreaded-application

Dan McGrath
Uh, I wonder how I didn't spot it. Anyway, it only mentions one tool and it's for .NET, so let's wait if there's anything similar for Java/C/C++.
Joonas Pulakka
+4  A: 

If you can run your tests under Linux, valgrind includes a tool called helgrind which purports to detect race conditions and potential deadlocks in programs that use pthreads; you might get some benefit from running your multithreaded code under that, since it will report potential errors even if they didn't actually occur in that particular test run.

Jeremy Friesner
+3  A: 

If the tested system is simple enough you could control the concurrency quite well by blocking operations in external mockup systems. This blocking can be done for example by waiting for some other operation to be started. If you can control all external calls this might work quite well by implementing different blocking sequences. I have tried this and it does reveal lock-level bugs quite well if you know possible problematic sequences well. And compared to many other concurrency testing it is quite deterministic. However this approach doesn't detect low level race conditions too well. I usually just go for load testing to find those, but I quess that isn't exactly unit testing.

I have seen these concurrency testing frameworks for .net, I'd assume its only matter of time before someone writes one for Java (hopefully).

And not to forget good old code reading. One of the best ways to find concurrency bugs is to just read through the code once again giving it your full concentration.

Lycha
Definitely the only way to come up with 100% correct software - concurrent or not - is to just do it right, think it through, double-check, and so on. No tool can do it for you. But in practice, given real-world constraints and that we're all human, any helpful tool is welcome when working in a difficult domain such as concurrent software.
Joonas Pulakka
+6  A: 

Most of the work I do these days involves multi-threaded and/or distributed systems. The majority of bugs involve "happens-before" type errors, where the developer assumes (wrongly) that event A will always happen before event B. But every 1000000th time the program is run, event B happens first, and this causes unpredictable behavior.

Additionally, there aren't really any good tools to detect timing issues, or even data corruption caused by race conditions. Tools like Helgrind and drd from the Valgrind toolkit work great for trivial programs, but they are not very useful in diagnosing large, complex systems. For one thing, they report false positives quite frequently (Helgrind especially). For another thing, it's difficult to actually detect certain errors while running under Helgrind/drd simply because programs running under Helgrind run almost 1000x slower, and you often need to run a program for quite a long time to even reproduce the race condition. Additionally, since running under Helgrind totally changes the timing of the program, it may become impossible to reproduce a certain timing issue. That's the problem with subtle timing issues; they're almost Heisenbergian in the sense that altering a program to detect timing issues may obscure the original issue.

The sad fact is, the human race still isn't adequately prepared to deal with complex, concurrent software. So unfortunately, there's no easy way to unit-test it. For distributed systems especially, you should plan your program carefully using Lamport's happens-before diagrams to help you identify the necessary order of events in your program. But ultimately, you can't really get away from brute-force unit testing with randomly varying inputs. It also helps to vary the frequency of thread context-switching during your unit-test by, e.g. running another background process which just takes up CPU cycles. Also, if you have access to a cluster, you can run multiple unit-tests in parallel, which can detect bugs much quicker and save you a lot of time.

Charles Salvia
Thanks for pointing out Lamport's diagrams! There are these newer "concurrent" languages that hide much of the complexity of the time domain by using immutable data structures exclusively (for instance, Clojure). But they're not quite cure-all solutions (yet).
Joonas Pulakka
+1  A: 

See my answer here.

Alex Siman
Thanks! Looks like this could be actually quite useful in some cases.
Joonas Pulakka
+1  A: 

Perhaps the answer is that you shouldn't. In concurrent systems, there may not always be a single deterministic answer that is correct.

Take the example of people boarding a train and choosing a seat. You are going to end up with different results everytime.

AndyM
But there are certainly answers that are not correct, even if there's more than one correct answer. If the boarding people start falling off the train seats, it's not a good result :-)
Joonas Pulakka