package net.sf.openrocket.util; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.ExceptionHandler; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TestMutex { private static final Logger log = LoggerFactory.getLogger(TestMutex.class); @Before public void setup() { System.setProperty("openrocket.debug.safetycheck", "true"); // Set exception handler that does nothing (called by SafetyMutex) Application.setExceptionHandler(new ExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { } @Override public void handleErrorCondition(Throwable exception) { } @Override public void handleErrorCondition(String message, Throwable exception) { } @Override public void handleErrorCondition(String message) { } }); } @Test public void testSingleLocking() { SafetyMutex.ConcreteSafetyMutex m = new SafetyMutex.ConcreteSafetyMutex(); // Test single locking assertNull(m.lockingThread); m.verify(); m.lock("here"); assertNotNull(m.lockingThread); assertTrue(m.unlock("here")); } @Test public void testDoubleLocking() { SafetyMutex.ConcreteSafetyMutex m = new SafetyMutex.ConcreteSafetyMutex(); // Test double locking m.verify(); m.lock("foobar"); m.verify(); m.lock("bazqux"); m.verify(); assertTrue(m.unlock("bazqux")); m.verify(); assertTrue(m.unlock("foobar")); m.verify(); } @Test public void testDoubleUnlocking() { SafetyMutex.ConcreteSafetyMutex m = new SafetyMutex.ConcreteSafetyMutex(); // Mark error reported to not init exception handler SafetyMutex.ConcreteSafetyMutex.errorReported = true; m.lock("here"); boolean unlocked = m.unlock("here"); assertTrue("First unlock failed but should have succeeded.", unlocked); log.error("***** The following ConcurrencyException in testDoubleUnlocking() is expected, but this test can't prevent it from being logged. *****"); unlocked = m.unlock("here"); assertFalse("Second unlock succeeded but should have failed.", unlocked); } private volatile int testState = 0; private volatile String failure = null; @Test(timeout = 1000) public void testThreadingErrors() { final SafetyMutex.ConcreteSafetyMutex m = new SafetyMutex.ConcreteSafetyMutex(); // Initialize and start the thread Thread thread = new Thread() { @Override public void run() { try { // Test locking a locked mutex waitFor(1); try { log.error("***** The following ConcurrencyException in testThreadingErrors() is expected, but this test can't prevent it from being logged. *****"); m.lock("in thread one"); failure = "Succeeded in locking a mutex locked by a different thread"; return; } catch (ConcurrencyException e) { // OK } // Test unlocking a mutex locked by a different thread log.error("***** The following ConcurrencyException in testThreadingErrors() is expected, but this test can't prevent it from being logged. *****"); if (m.unlock("in thread two")) { failure = "Succeeded in unlocking a mutex locked by a different thread"; return; } // Test verifying a locked mutex that already has an error try { log.error("***** The following ConcurrencyException in testThreadingErrors() is expected, but this test can't prevent it from being logged. *****"); m.verify(); failure = "Succeeded in verifying a mutex locked by a different thread"; return; } catch (ConcurrencyException e) { // OK } // Test locking a mutex after it's been unlocked testState = 2; waitFor(3); m.lock("in thread three"); m.verify(); // Wait for other side to test testState = 4; waitFor(5); // Exit code testState = 6; } catch (Exception e) { e.printStackTrace(); failure = "Exception occurred in thread: " + e; return; } } }; thread.setDaemon(true); thread.start(); m.lock("one"); testState = 1; waitFor(2); assertNull("Thread error: " + failure, failure); m.verify(); m.unlock("one"); testState = 3; waitFor(4); assertNull("Thread error: " + failure, failure); try { log.error("***** The following ConcurrencyException in testThreadingErrors() is expected, but this test can't prevent it from being logged. *****"); m.lock("two"); fail("Succeeded in locking a locked mutex in main thread"); } catch (ConcurrencyException e) { // OK } // Test unlocking a mutex locked by a different thread log.error("***** The following ConcurrencyException in testThreadingErrors() is expected, but this test can't prevent it from being logged. *****"); assertFalse(m.unlock("here")); try { log.error("***** The following ConcurrencyException in testThreadingErrors() is expected, but this test can't prevent it from being logged. *****"); m.verify(); fail("Succeeded in verifying a locked mutex in main thread"); } catch (ConcurrencyException e) { // OK } testState = 5; waitFor(6); assertNull("Thread error: " + failure, failure); } private void waitFor(int state) { while (testState != state && failure == null) { try { Thread.sleep(1); } catch (InterruptedException e) { } } } public void testBogusMutex() { SafetyMutex m = new SafetyMutex.BogusSafetyMutex(); m.lock("foo"); m.lock("bar"); m.lock("baz"); m.verify(); m.unlock("a"); m.unlock(null); m.unlock(""); m.unlock("c"); } }