3

I have fields that manage information that multiple threads potentially work with. Access to these fields is critical so I instantiated a semaphore:

Semaphore semaphore = new Semaphore(1);

Every method tries to acquire the semaphore, essentially locking if none is available:

public void method()
{
    semaphore.acquire();
    doStruff();
    cleanUp()
}

//Other methods using the same basic layout as the method above.

public void cleanUp()
{
    //watch for state consistency and do some clean up
    semaphore.release();
} 

How would I go about testing the above knowing that:

  • I don't want to use timed testing, using Thread.sleep(x) due to the inherent problems with that method such as the dependence on the clock and lack in trust of that method.
  • I cannot use MultithreadedTC, at least not that I know of, since one thread will always be executing, hence, the metronome won't advance to the next tick.
  • I don't want to modify existing code to add bits just for testing purposes. The code should stay clear of additions from testing.
  • I would want something solid that I can run multiple times and use in regression testing.
  • As I am not the only one using the code, it should be Java native and not use a scripting language such as ConAn.

The bit of code is small enough that I can, with a good certainty, say that it won't deadlock or the condition detailed above violated. However, a good test case could lend more weight to it and certainly add more confidence.

Eric Tobias
  • 3,225
  • 4
  • 32
  • 50
  • Just a passing remark: taking and releasing the semaphore in different functions is asking for trouble. Also, I don't see why you use a `semaphore` when you have a `lock` available, that is much more suited to the task: it will handle priority inversion (if your JRE supports it) and can be taken twice by the same thread if need be. Finally, I don't see how you will manage to test your critical section without adding specific code to (1) make sure a concurrent access attempt occurs (2) the protected data are still in a consistent state after the collision was avoided. – kuroi neko Jan 23 '14 at 03:12

2 Answers2

3

This is an example of a test-smell which arguably should motivate code change. The code is difficult to test because it's got synchronisation responsibilities mixed up in application logic responsibilities. Generally, it's very difficult to ensure that such code is acting correctly.

It would probably be best to extract the synchronisation related code into a separate class, which is testable in itself and use dependency injection to allow tests to mock that interface.

e.g.

public interface ResourceGuard<T> {
    void accessResource(Consumer<T> consumer);
}

public class ExclusiveResourceGuard<T> implements ResourceGuard<T> {
    private final T instance;
    private final Semaphore semaphore;

    public ExclusiveResourceGuard(final T instance) {
        this.instance = instance;
    }

    public void accessResource(Consumer<T> consumer) {
        semaphore.acquire();
        consumer.consume(instance);
        semaphore.release();
    }
}

You can now test this class by passing in a fake consumer which acts on test-scoped data and ensure that access is properly synchronised.

Then your production consumer class can have an injected resource guard...

public class StuffDoingClass {
    private final ResourceGuard<MyThing> myThing;

    public StuffDoingClass(final ResourceGuard<MyThing> theThing) {
        this.myThing = theThing;
    }

    public void method() {
        this.myThing.accessResource(this::doStuff);
    }

    private void doStuff(final MyThing theActualThing) {
        theActualThing.method(...);
    }
}

And now this class is testable with a simple enough mock of ResourceGuard.

From a design perspective it's also more difficult to access the resource mistakenly (without holding the lock). I know this answer goes against your 'no-code-change' mandate but in this particular case it seems very unlikely you can have all of your requirements met.

sisyphus
  • 6,174
  • 1
  • 25
  • 35
  • 1
    While it has been almost two years, I have had time to re-evaluate a lot of my design decision from "back in the day"! Thank you for your answer, I actually think it rather elegant. A quick question though; Consumer as in Java 1.8's Consumer? – Eric Tobias Dec 22 '15 at 13:56
  • Yeah, I was using Java8 Consumer as an example. You could substitute any kind of interface you want depending on your use case, including using Guava versions of the same if you're not using Java8. – sisyphus Dec 22 '15 at 14:16
-1

Take a look at JMeter.

It will allow you to fire up multiple instances of your code in many various ways. To ensure you are definitely seeing multi-threaded access, consider using more than one thread group in your JMeter test plan.

FYI: I find it useful when threading to synchronize on the actual object that is going to be updated. This allows you to control the updating of more than one variable in a controlled manner and is very self-documenting. It looks like this...

synchronize(variable) {

}

The above can appear in pretty much any method, even constructors.

Rodney P. Barbati
  • 1,883
  • 24
  • 18
  • Each synchronize block creates an internal lock associated with the variable passed as argument. If you have to update a set of objects that must remain consistent with each other, you have to handle a global lock protecting the whole set manually. – kuroi neko Jan 23 '14 at 03:09
  • 1
    I would suggest that the whole set of values needing to be consistent would be in a class specifically created to maintain that consistency, and that you synchronize on the instance of that class. – Rodney P. Barbati Jan 24 '14 at 18:31
  • Sure, that would be a clean way of doing things, but that might prove difficult in some cases, like [this one](http://stackoverflow.com/questions/21247283/fast-reading-writing-from-to-int-array-concurrently/21298862#21298862) for instance. Since the OP did not respond, I am not quite sure of the validity of what I wrote there. What do you think of it? – kuroi neko Jan 25 '14 at 06:30