From: Charles R. <cr...@ri...> - 2003-01-31 21:59:43
|
Note: This email is primarily for students in 312, but it should be of interest to all DrJava developers. Eliot brought up a good question in today's lab session, while looking at a test case that involved multithreaded code. (Actually, it was testing calls between two different JVMs, which is an extreme case of multithreaded code, but which happens often in DrJava.) It made me realize I should explain the technique we use to test such behavior, as you guys are starting to write your unit tests. (Note: if you have never worked with multithreaded code before, you'll want to see me for a crash course, because it shows up a *lot* in DrJava!) The Situation ----- Suppose you have the following classes that you're testing: class Foo { Bar b = ... void foo() { b.bar(); } } interface FooListener { void done(); } class Bar { FooListener fl = ... void bar() { ...; fl.done(); } } These classes run in different threads or JVMs. Essentially, Foo tells Bar to do some work (perhaps over RMI), and Bar lets FooListener know when it's done. How To Test ----- We use a synchronization block in our test to help us wait for Bar to call done(), since we don't know exactly when it's going to happen. We will create a special FooListener for this purpose. public class FooBarTest extends TestCase { Foo f = ...; Bar b = ...; public void testFooBar() { // Create a listener for our test FooListener fl = new FooListener() { void done() { synchronized(this) { ... this.notify(); } } }; b.addListener(fl); synchronized(fl) { f.foo(); fl.wait(); } b.removeListener(fl); } } Things To Notice: In our test case, we create a FooListener that synchronizes on itself in the done() method. We add that listener to our Bar, and then call f.foo() in a block of code that is also synchronized on the listener. This means that we've acquired the lock on the listener, so *nothing* can be executed in our listener's done() method until we release that lock. Note that we have *no idea* when done() will be called. The OS might schedule the Bar code to run before we can call fl.wait(), so done() might get called immediately. Or the OS might not schedule it for several seconds, in which case we've already called wait(). That's why we need to have the lock. Even if done() gets called first, it *can't do anything* until we release the lock by calling fl.wait(). Then done() can finish at its own convenience, and notify the waiting test method when it is finished. This construct is used everywhere in DrJava's unit tests, so it is very important that you understand it, to avoid creating timing bugs and synchronization problems. If you haven't had any experience with multithreaded code before, please make an appointment to come talk to me and I can help walk you through it. If you want to look at some of this code in practice, here are a few good tests to browse: edu.rice.cs.util.NewJVMTest (uses a TestJVMExtension class rather than a listener) edu.rice.cs.drjava.model.GlobalModelOtherTest (waits/notifies on certain events on a GlobalModelListener) Let me know if you have any questions, or if this isn't clear! I'll be adding some of this to the developer documentation... Charlie |