diff --git a/api/net/sf/briar/api/lifecycle/ShutdownManager.java b/api/net/sf/briar/api/lifecycle/ShutdownManager.java new file mode 100644 index 0000000000000000000000000000000000000000..8d8b0af405feb380b4947929e882efbda47d9721 --- /dev/null +++ b/api/net/sf/briar/api/lifecycle/ShutdownManager.java @@ -0,0 +1,16 @@ +package net.sf.briar.api.lifecycle; + +public interface ShutdownManager { + + /** + * Registers a hook to be run when the JVM shuts down and returns a handle + * that can be used to remove the hook. + */ + int addShutdownHook(Runnable hook); + + /** + * Removes the shutdown hook identified by the given handle and returns + * true if the hook was removed. + */ + boolean removeShutdownHook(int handle); +} diff --git a/build-common.xml b/build-common.xml index 7d7504ff662d459ed455ea5edbf88f1ab3b98967..681fdb9a105be02cc5a47087ac0a33ac2dc637a8 100644 --- a/build-common.xml +++ b/build-common.xml @@ -21,6 +21,7 @@ </path> <target name='clean'> <delete dir='build'/> + <delete dir='test.tmp'/> </target> <target name='compile'> <mkdir dir='build'/> diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java index d8b2d5c8d40a4f8ae121d54a1ab7867d1264e48d..92420cca1de7ac407f22a6bd7a6111233dbce32a 100644 --- a/components/net/sf/briar/db/DatabaseComponentImpl.java +++ b/components/net/sf/briar/db/DatabaseComponentImpl.java @@ -16,6 +16,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; import net.sf.briar.api.Bytes; import net.sf.briar.api.ContactId; @@ -38,6 +40,7 @@ import net.sf.briar.api.db.event.RatingChangedEvent; import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent; import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent; import net.sf.briar.api.db.event.TransportAddedEvent; +import net.sf.briar.api.lifecycle.ShutdownManager; import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.Batch; @@ -73,6 +76,9 @@ import com.google.inject.Inject; class DatabaseComponentImpl<T> implements DatabaseComponent, DatabaseCleaner.Callback { + private static final Logger LOG = + Logger.getLogger(DatabaseComponentImpl.class.getName()); + /* * Locks must always be acquired in alphabetical order. See the Database * interface to find out which calls require which locks. @@ -97,27 +103,61 @@ DatabaseCleaner.Callback { private final Database<T> db; private final DatabaseCleaner cleaner; + private final ShutdownManager shutdown; private final List<DatabaseListener> listeners = new ArrayList<DatabaseListener>(); // Locking: self + private final Object spaceLock = new Object(); private long bytesStoredSinceLastCheck = 0L; // Locking: spaceLock private long timeOfLastCheck = 0L; // Locking: spaceLock + private final Object openCloseLock = new Object(); + private boolean open = false; // Locking: openCloseLock; + private int shutdownHandle = -1; // Locking: openCloseLock; + @Inject - DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner) { + DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner, + ShutdownManager shutdown) { this.db = db; this.cleaner = cleaner; + this.shutdown = shutdown; } public void open(boolean resume) throws DbException, IOException { - db.open(resume); - cleaner.startCleaning(this, MAX_MS_BETWEEN_SPACE_CHECKS); + synchronized(openCloseLock) { + if(open) throw new IllegalStateException(); + open = true; + db.open(resume); + cleaner.startCleaning(this, MAX_MS_BETWEEN_SPACE_CHECKS); + shutdownHandle = shutdown.addShutdownHook(new Runnable() { + public void run() { + try { + synchronized(openCloseLock) { + shutdownHandle = -1; + close(); + } + } catch(DbException e) { + if(LOG.isLoggable(Level.WARNING)) + LOG.warning(e.getMessage()); + } catch(IOException e) { + if(LOG.isLoggable(Level.WARNING)) + LOG.warning(e.getMessage()); + } + } + }); + } } public void close() throws DbException, IOException { - cleaner.stopCleaning(); - db.close(); + synchronized(openCloseLock) { + if(!open) return; + open = false; + if(shutdownHandle != -1) + shutdown.removeShutdownHook(shutdownHandle); + cleaner.stopCleaning(); + db.close(); + } } public void addListener(DatabaseListener d) { diff --git a/components/net/sf/briar/db/DatabaseModule.java b/components/net/sf/briar/db/DatabaseModule.java index 9df6352ecdad7be6c56b4ac7d66cbe3c7e2f725c..ab58a5efa595395db09bab9bc44133f9b5554447 100644 --- a/components/net/sf/briar/db/DatabaseModule.java +++ b/components/net/sf/briar/db/DatabaseModule.java @@ -8,6 +8,7 @@ import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseDirectory; import net.sf.briar.api.db.DatabaseMaxSize; import net.sf.briar.api.db.DatabasePassword; +import net.sf.briar.api.lifecycle.ShutdownManager; import net.sf.briar.api.protocol.GroupFactory; import net.sf.briar.api.transport.ConnectionContextFactory; import net.sf.briar.api.transport.ConnectionWindowFactory; @@ -35,7 +36,7 @@ public class DatabaseModule extends AbstractModule { @Provides @Singleton DatabaseComponent getDatabaseComponent(Database<Connection> db, - DatabaseCleaner cleaner) { - return new DatabaseComponentImpl<Connection>(db, cleaner); + DatabaseCleaner cleaner, ShutdownManager shutdown) { + return new DatabaseComponentImpl<Connection>(db, cleaner, shutdown); } } diff --git a/components/net/sf/briar/lifecycle/LifecycleModule.java b/components/net/sf/briar/lifecycle/LifecycleModule.java new file mode 100644 index 0000000000000000000000000000000000000000..241e41a09ae3963d87ceb47586e8d975744b3031 --- /dev/null +++ b/components/net/sf/briar/lifecycle/LifecycleModule.java @@ -0,0 +1,16 @@ +package net.sf.briar.lifecycle; + +import net.sf.briar.api.lifecycle.ShutdownManager; +import net.sf.briar.util.OsUtils; + +import com.google.inject.AbstractModule; + +public class LifecycleModule extends AbstractModule { + + @Override + protected void configure() { + if(OsUtils.isWindows()) + bind(ShutdownManager.class).to(WindowsShutdownManagerImpl.class); + else bind(ShutdownManager.class).to(ShutdownManagerImpl.class); + } +} diff --git a/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java b/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..001f8a3f0a4a2cbf03770e1360d99e59ff7fc7b7 --- /dev/null +++ b/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java @@ -0,0 +1,31 @@ +package net.sf.briar.lifecycle; + +import java.util.HashMap; +import java.util.Map; + +import net.sf.briar.api.lifecycle.ShutdownManager; + +class ShutdownManagerImpl implements ShutdownManager { + + protected final Map<Integer, Thread> hooks; // Locking: this + + private int nextHandle = 0; // Locking: this + + ShutdownManagerImpl() { + hooks = new HashMap<Integer, Thread>(); + } + + public synchronized int addShutdownHook(Runnable runnable) { + int handle = nextHandle++; + Thread hook = new Thread(runnable); + hooks.put(handle, hook); + Runtime.getRuntime().addShutdownHook(hook); + return handle; + } + + public synchronized boolean removeShutdownHook(int handle) { + Thread hook = hooks.remove(handle); + if(hook == null) return false; + else return Runtime.getRuntime().removeShutdownHook(hook); + } +} diff --git a/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java b/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..7d8939c340365038da773a0f0a5a3113c4153754 --- /dev/null +++ b/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java @@ -0,0 +1,142 @@ +package net.sf.briar.lifecycle; + +import java.awt.HeadlessException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.JFrame; + +import net.sf.briar.util.OsUtils; + +import com.sun.jna.Native; +import com.sun.jna.platform.win32.WinDef.HWND; +import com.sun.jna.platform.win32.WinDef.LPARAM; +import com.sun.jna.platform.win32.WinDef.LRESULT; +import com.sun.jna.platform.win32.WinDef.WPARAM; +import com.sun.jna.win32.StdCallLibrary; +import com.sun.jna.win32.StdCallLibrary.StdCallCallback; + +class WindowsShutdownManagerImpl extends ShutdownManagerImpl { + + private static final Logger LOG = + Logger.getLogger(WindowsShutdownManagerImpl.class.getName()); + + private static final int WM_QUERYENDSESSION = 17; + private static final int WM_ENDSESSION = 22; + private static final int GWL_WNDPROC = -4; + + private boolean initialised = false; // Locking: this + + @Override + public synchronized int addShutdownHook(Runnable runnable) { + if(!initialised) initialise(); + return super.addShutdownHook(new RunOnce(runnable)); + } + + // Locking: this + private void initialise() { + if(OsUtils.isWindows()) { + try { + HWND hwnd = new HWND(); + hwnd.setPointer(Native.getComponentPointer(new JFrame())); + User32 u = (User32) Native.loadLibrary("user32", User32.class); + try { + // Load the 64-bit functions + setCallback64Bit(u, hwnd); + } catch(UnsatisfiedLinkError e) { + // Load the 32-bit functions + setCallback32Bit(u, hwnd); + } + } catch(HeadlessException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + } catch(UnsatisfiedLinkError e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + } + } else { + if(LOG.isLoggable(Level.WARNING)) + LOG.warning("Windows shutdown manager used on non-Windows OS"); + } + initialised = true; + } + + private void setCallback64Bit(final User32 user32, HWND hwnd) { + final WindowProc oldProc = user32.GetWindowLongPtrW(hwnd, GWL_WNDPROC); + WindowProc newProc = new WindowProc() { + public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp) { + if(msg == WM_QUERYENDSESSION) { + // It's safe to delay returning from this message + runShutdownHooks(); + } else if(msg == WM_ENDSESSION) { + // Return immediately or the JVM crashes on return + } + return user32.CallWindowProcPtrW(oldProc, hwnd, msg, wp, lp); + } + }; + user32.SetWindowLongPtrW(hwnd, GWL_WNDPROC, newProc); + } + + private void setCallback32Bit(final User32 user32, HWND hwnd) { + final WindowProc oldProc = user32.GetWindowLongW(hwnd, GWL_WNDPROC); + WindowProc newProc = new WindowProc() { + public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp) { + if(msg == WM_QUERYENDSESSION) { + // It's safe to delay returning from this message + runShutdownHooks(); + } else if(msg == WM_ENDSESSION) { + // Return immediately or the JVM crashes on return + } + return user32.CallWindowProcW(oldProc, hwnd, msg, wp, lp); + } + }; + user32.SetWindowLongW(hwnd, GWL_WNDPROC, newProc); + } + + // Package access for testing + synchronized void runShutdownHooks() { + // Start each hook in its own thread + for(Thread hook : hooks.values()) hook.start(); + // Wait for all the hooks to finish + for(Thread hook : hooks.values()) { + try { + hook.join(); + } catch(InterruptedException e) { + if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage()); + } + } + } + + private static class RunOnce implements Runnable { + + private final Runnable runnable; + private final AtomicBoolean called = new AtomicBoolean(false); + + private RunOnce(Runnable runnable) { + this.runnable = runnable; + } + + public void run() { + if(called.getAndSet(true)) return; + runnable.run(); + } + } + + private static interface User32 extends StdCallLibrary { + + LRESULT CallWindowProcW(WindowProc oldProc, HWND hwnd, int msg, + WPARAM wp, LPARAM lp); + LRESULT CallWindowProcPtrW(WindowProc oldProc, HWND hwnd, int msg, + WPARAM wp, LPARAM lp); + + WindowProc GetWindowLongW(HWND hwnd, int index); + WindowProc GetWindowLongPtrW(HWND hwnd, int index); + + LRESULT SetWindowLongW(HWND hwnd, int index, WindowProc newProc); + LRESULT SetWindowLongPtrW(HWND hwnd, int index, WindowProc newProc); + } + + private static interface WindowProc extends StdCallCallback { + + public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp); + } +} diff --git a/components/net/sf/briar/plugins/socket/LanSocketPlugin.java b/components/net/sf/briar/plugins/socket/LanSocketPlugin.java index 4cd0d9efbd39bee518d059f21a3b7b936e76f78b..181a9b8ff3c73c69438cf164f36106976706c209 100644 --- a/components/net/sf/briar/plugins/socket/LanSocketPlugin.java +++ b/components/net/sf/briar/plugins/socket/LanSocketPlugin.java @@ -19,7 +19,7 @@ import net.sf.briar.api.transport.StreamTransportConnection; import net.sf.briar.util.ByteUtils; /** A socket plugin that supports exchanging invitations over a LAN. */ -public class LanSocketPlugin extends SimpleSocketPlugin { +class LanSocketPlugin extends SimpleSocketPlugin { private static final Logger LOG = Logger.getLogger(LanSocketPlugin.class.getName()); diff --git a/components/net/sf/briar/transport/ConnectionDispatcherImpl.java b/components/net/sf/briar/transport/ConnectionDispatcherImpl.java index adbb5ad45d85091287a93198c26eca1fae1f29da..54cce8ab0d0288a4aae340c87a9283cac7b17d6b 100644 --- a/components/net/sf/briar/transport/ConnectionDispatcherImpl.java +++ b/components/net/sf/briar/transport/ConnectionDispatcherImpl.java @@ -22,7 +22,7 @@ import net.sf.briar.api.transport.TransportConstants; import com.google.inject.Inject; -public class ConnectionDispatcherImpl implements ConnectionDispatcher { +class ConnectionDispatcherImpl implements ConnectionDispatcher { private static final Logger LOG = Logger.getLogger(ConnectionDispatcherImpl.class.getName()); diff --git a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java index 940b37c0934cf8c75cb62a77f384d74c1dd0aab9..34eacc8684d855672cf81416bb4bd3f8de2af447 100644 --- a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java +++ b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java @@ -28,6 +28,7 @@ import net.sf.briar.api.db.event.DatabaseEvent; import net.sf.briar.api.db.event.DatabaseListener; import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent; import net.sf.briar.api.db.event.TransportAddedEvent; +import net.sf.briar.api.lifecycle.ShutdownManager; import net.sf.briar.api.protocol.Transport; import net.sf.briar.api.protocol.TransportId; import net.sf.briar.api.protocol.TransportIndex; @@ -46,6 +47,7 @@ DatabaseListener { private final CryptoComponent crypto; private final DatabaseComponent db; private final Executor executor; + private final ShutdownManager shutdown; private final Cipher ivCipher; // Locking: this private final Map<Bytes, Context> expected; // Locking: this @@ -53,10 +55,11 @@ DatabaseListener { @Inject ConnectionRecogniserImpl(CryptoComponent crypto, DatabaseComponent db, - Executor executor) { + Executor executor, ShutdownManager shutdown) { this.crypto = crypto; this.db = db; this.executor = executor; + this.shutdown = shutdown; ivCipher = crypto.getIvCipher(); expected = new HashMap<Bytes, Context>(); db.addListener(this); @@ -64,8 +67,8 @@ DatabaseListener { // Locking: this private void initialise() throws DbException { - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override + assert !initialised; + shutdown.addShutdownHook(new Runnable() { public void run() { eraseSecrets(); } diff --git a/components/net/sf/briar/transport/stream/IncomingStreamConnection.java b/components/net/sf/briar/transport/stream/IncomingStreamConnection.java index d3da63f21c29ac3d4e241d9a0bf0589ccb6ba363..994693d0b79ecbfdeaf856af24965e7693614728 100644 --- a/components/net/sf/briar/transport/stream/IncomingStreamConnection.java +++ b/components/net/sf/briar/transport/stream/IncomingStreamConnection.java @@ -13,7 +13,7 @@ import net.sf.briar.api.transport.ConnectionWriter; import net.sf.briar.api.transport.ConnectionWriterFactory; import net.sf.briar.api.transport.StreamTransportConnection; -public class IncomingStreamConnection extends StreamConnection { +class IncomingStreamConnection extends StreamConnection { private final ConnectionContext ctx; private final byte[] encryptedIv; diff --git a/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java b/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java index 178190e2a131e79f12e750942f97bd824cf6ebcf..ba37e28def8261a5ece29bcdc17cac8adb950242 100644 --- a/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java +++ b/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java @@ -15,7 +15,7 @@ import net.sf.briar.api.transport.ConnectionWriter; import net.sf.briar.api.transport.ConnectionWriterFactory; import net.sf.briar.api.transport.StreamTransportConnection; -public class OutgoingStreamConnection extends StreamConnection { +class OutgoingStreamConnection extends StreamConnection { private final TransportIndex transportIndex; diff --git a/components/net/sf/briar/transport/stream/StreamConnectionFactoryImpl.java b/components/net/sf/briar/transport/stream/StreamConnectionFactoryImpl.java index cfaf8fe74718a80a8f7d954af5d98fc14ee43ee3..514a702302eb119ded31b24a2e62614c5aaee246 100644 --- a/components/net/sf/briar/transport/stream/StreamConnectionFactoryImpl.java +++ b/components/net/sf/briar/transport/stream/StreamConnectionFactoryImpl.java @@ -13,7 +13,7 @@ import net.sf.briar.api.transport.StreamTransportConnection; import com.google.inject.Inject; -public class StreamConnectionFactoryImpl implements StreamConnectionFactory { +class StreamConnectionFactoryImpl implements StreamConnectionFactory { private final ConnectionReaderFactory connReaderFactory; private final ConnectionWriterFactory connWriterFactory; diff --git a/test/build.xml b/test/build.xml index 2644843b78feab0845136cee55befdd64c8bc34b..b3c7132f56dd40102bc7cb3a674430df9239ba7e 100644 --- a/test/build.xml +++ b/test/build.xml @@ -26,6 +26,7 @@ <test name='net.sf.briar.i18n.FontManagerTest'/> <test name='net.sf.briar.i18n.I18nTest'/> <test name='net.sf.briar.invitation.InvitationWorkerTest'/> + <test name='net.sf.briar.lifecycle.ShutdownManagerImplTest'/> <test name='net.sf.briar.plugins.PluginManagerImplTest'/> <test name='net.sf.briar.plugins.file.LinuxRemovableDriveFinderTest'/> <test name='net.sf.briar.plugins.file.MacRemovableDriveFinderTest'/> diff --git a/test/net/sf/briar/ProtocolIntegrationTest.java b/test/net/sf/briar/ProtocolIntegrationTest.java index cd0ad582f35525d2897c2c14fdade3283001f7ac..1ed4eb0fb730b1bd17135afbd85f378887c9f4d4 100644 --- a/test/net/sf/briar/ProtocolIntegrationTest.java +++ b/test/net/sf/briar/ProtocolIntegrationTest.java @@ -54,6 +54,7 @@ import net.sf.briar.api.transport.ConnectionWriter; import net.sf.briar.api.transport.ConnectionWriterFactory; import net.sf.briar.crypto.CryptoModule; import net.sf.briar.db.DatabaseModule; +import net.sf.briar.lifecycle.LifecycleModule; import net.sf.briar.protocol.ProtocolModule; import net.sf.briar.protocol.writers.ProtocolWritersModule; import net.sf.briar.serial.SerialModule; @@ -102,10 +103,11 @@ public class ProtocolIntegrationTest extends TestCase { } }; Injector i = Guice.createInjector(testModule, new CryptoModule(), - new DatabaseModule(), new ProtocolModule(), - new ProtocolWritersModule(), new SerialModule(), - new TestDatabaseModule(), new TransportBatchModule(), - new TransportModule(), new TransportStreamModule()); + new DatabaseModule(), new LifecycleModule(), + new ProtocolModule(), new ProtocolWritersModule(), + new SerialModule(), new TestDatabaseModule(), + new TransportBatchModule(), new TransportModule(), + new TransportStreamModule()); connectionContextFactory = i.getInstance(ConnectionContextFactory.class); connectionReaderFactory = i.getInstance(ConnectionReaderFactory.class); diff --git a/test/net/sf/briar/db/DatabaseComponentImplTest.java b/test/net/sf/briar/db/DatabaseComponentImplTest.java index da67ef8b4d4855df0777a2ddcf371faddc548d3f..0a2c3f612c0b39b65dd90e4c5a240419a1bfbc11 100644 --- a/test/net/sf/briar/db/DatabaseComponentImplTest.java +++ b/test/net/sf/briar/db/DatabaseComponentImplTest.java @@ -7,6 +7,7 @@ import java.util.Collections; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; +import net.sf.briar.api.lifecycle.ShutdownManager; import net.sf.briar.db.DatabaseCleaner.Callback; import org.jmock.Expectations; @@ -25,11 +26,12 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE)); }}); - Callback db = createDatabaseComponentImpl(database, cleaner); + Callback db = createDatabaseComponentImpl(database, cleaner, shutdown); db.checkFreeSpaceAndClean(); @@ -42,6 +44,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE - 1)); @@ -54,7 +57,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE)); }}); - Callback db = createDatabaseComponentImpl(database, cleaner); + Callback db = createDatabaseComponentImpl(database, cleaner, shutdown); db.checkFreeSpaceAndClean(); @@ -68,6 +71,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE - 1)); @@ -82,7 +86,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE)); }}); - Callback db = createDatabaseComponentImpl(database, cleaner); + Callback db = createDatabaseComponentImpl(database, cleaner, shutdown); db.checkFreeSpaceAndClean(); @@ -96,6 +100,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE - 1)); @@ -112,7 +117,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { oneOf(database).getFreeSpace(); will(returnValue(MIN_FREE_SPACE)); }}); - Callback db = createDatabaseComponentImpl(database, cleaner); + Callback db = createDatabaseComponentImpl(database, cleaner, shutdown); db.checkFreeSpaceAndClean(); @@ -121,12 +126,14 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest { @Override protected <T> DatabaseComponent createDatabaseComponent( - Database<T> database, DatabaseCleaner cleaner) { - return createDatabaseComponentImpl(database, cleaner); + Database<T> database, DatabaseCleaner cleaner, + ShutdownManager shutdown) { + return createDatabaseComponentImpl(database, cleaner, shutdown); } private <T> DatabaseComponentImpl<T> createDatabaseComponentImpl( - Database<T> database, DatabaseCleaner cleaner) { - return new DatabaseComponentImpl<T>(database, cleaner); + Database<T> database, DatabaseCleaner cleaner, + ShutdownManager shutdown) { + return new DatabaseComponentImpl<T>(database, cleaner, shutdown); } } diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java index 58c95b8a92d7710259d100fb4ba5249d5cd694f2..e17b18ed1aebcf201e7c01be909ce57a923b0151 100644 --- a/test/net/sf/briar/db/DatabaseComponentTest.java +++ b/test/net/sf/briar/db/DatabaseComponentTest.java @@ -22,6 +22,7 @@ import net.sf.briar.api.db.event.DatabaseListener; import net.sf.briar.api.db.event.MessagesAddedEvent; import net.sf.briar.api.db.event.RatingChangedEvent; import net.sf.briar.api.db.event.TransportAddedEvent; +import net.sf.briar.api.lifecycle.ShutdownManager; import net.sf.briar.api.protocol.Ack; import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.Batch; @@ -103,14 +104,17 @@ public abstract class DatabaseComponentTest extends TestCase { } protected abstract <T> DatabaseComponent createDatabaseComponent( - Database<T> database, DatabaseCleaner cleaner); + Database<T> database, DatabaseCleaner cleaner, + ShutdownManager shutdown); @Test @SuppressWarnings("unchecked") public void testSimpleCalls() throws Exception { + final int shutdownHandle = 12345; Mockery context = new Mockery(); final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final ConnectionWindow connectionWindow = context.mock(ConnectionWindow.class); final Group group = context.mock(Group.class); @@ -124,6 +128,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(cleaner).startCleaning( with(any(DatabaseCleaner.Callback.class)), with(any(long.class))); + oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); + will(returnValue(shutdownHandle)); // getRating(authorId) oneOf(database).getRating(txn, authorId); will(returnValue(Rating.UNRATED)); @@ -189,10 +195,12 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).removeContact(txn, contactId); oneOf(listener).eventOccurred(with(any(ContactRemovedEvent.class))); // close() + oneOf(shutdown).removeShutdownHook(shutdownHandle); oneOf(cleaner).stopCleaning(); oneOf(database).close(); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.open(false); db.addListener(listener); @@ -224,6 +232,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ // setRating(authorId, Rating.GOOD) allowing(database).startTransaction(); @@ -241,7 +250,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(null)); oneOf(database).commitTransaction(txn); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.setRating(authorId, Rating.GOOD); @@ -254,6 +264,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ // setRating(authorId, Rating.GOOD) oneOf(database).startTransaction(); @@ -275,7 +286,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).setSendability(txn, parentId, 2); oneOf(database).commitTransaction(txn); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.setRating(authorId, Rating.GOOD); @@ -289,6 +301,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ // setRating(authorId, Rating.GOOD) oneOf(database).startTransaction(); @@ -313,7 +326,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(null)); oneOf(database).commitTransaction(txn); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.setRating(authorId, Rating.GOOD); @@ -327,6 +341,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) oneOf(database).startTransaction(); @@ -335,7 +350,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(false)); oneOf(database).commitTransaction(txn); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addLocalGroupMessage(message); @@ -348,6 +364,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) oneOf(database).startTransaction(); @@ -358,7 +375,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(false)); oneOf(database).commitTransaction(txn); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addLocalGroupMessage(message); @@ -371,6 +389,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) oneOf(database).startTransaction(); @@ -390,7 +409,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).setSendability(txn, messageId, 0); oneOf(database).commitTransaction(txn); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addLocalGroupMessage(message); @@ -404,6 +424,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) oneOf(database).startTransaction(); @@ -426,7 +447,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(null)); oneOf(database).commitTransaction(txn); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addLocalGroupMessage(message); @@ -439,6 +461,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -449,7 +472,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).addPrivateMessage(txn, privateMessage, contactId); will(returnValue(false)); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addLocalPrivateMessage(privateMessage, contactId); @@ -462,6 +486,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -473,7 +498,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(true)); oneOf(database).setStatus(txn, contactId, messageId, Status.NEW); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addLocalPrivateMessage(privateMessage, contactId); @@ -487,6 +513,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final AckWriter ackWriter = context.mock(AckWriter.class); final BatchWriter batchWriter = context.mock(BatchWriter.class); final OfferWriter offerWriter = context.mock(OfferWriter.class); @@ -510,7 +537,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(false)); exactly(19).of(database).commitTransaction(txn); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); try { db.addLocalPrivateMessage(privateMessage, contactId); @@ -621,6 +649,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final AckWriter ackWriter = context.mock(AckWriter.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -641,7 +670,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).removeBatchesToAck(txn, contactId, Collections.singletonList(batchId)); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.generateAck(contactId, ackWriter); @@ -659,6 +689,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final BatchWriter batchWriter = context.mock(BatchWriter.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -688,7 +719,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).addOutstandingBatch(txn, contactId, batchId, sendable); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.generateBatch(contactId, batchWriter); @@ -708,6 +740,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final BatchWriter batchWriter = context.mock(BatchWriter.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -734,7 +767,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).addOutstandingBatch(txn, contactId, batchId, Collections.singletonList(messageId1)); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.generateBatch(contactId, batchWriter, requested); @@ -751,6 +785,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final OfferWriter offerWriter = context.mock(OfferWriter.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -768,7 +803,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(false)); oneOf(offerWriter).finish(); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); assertEquals(Collections.singletonList(messageId), db.generateOffer(contactId, offerWriter)); @@ -783,6 +819,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final SubscriptionUpdateWriter subscriptionUpdateWriter = context.mock(SubscriptionUpdateWriter.class); context.checking(new Expectations() {{ @@ -797,7 +834,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).getSubscriptionsSent(txn, contactId); will(returnValue(now)); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.generateSubscriptionUpdate(contactId, subscriptionUpdateWriter); @@ -814,6 +852,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final SubscriptionUpdateWriter subscriptionUpdateWriter = context.mock(SubscriptionUpdateWriter.class); context.checking(new Expectations() {{ @@ -837,7 +876,8 @@ public abstract class DatabaseComponentTest extends TestCase { with(Collections.singletonMap(group, 0L)), with(any(long.class))); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.generateSubscriptionUpdate(contactId, subscriptionUpdateWriter); @@ -851,6 +891,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final TransportUpdateWriter transportUpdateWriter = context.mock(TransportUpdateWriter.class); context.checking(new Expectations() {{ @@ -865,7 +906,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).getTransportsSent(txn, contactId); will(returnValue(now)); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.generateTransportUpdate(contactId, transportUpdateWriter); @@ -882,6 +924,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final TransportUpdateWriter transportUpdateWriter = context.mock(TransportUpdateWriter.class); context.checking(new Expectations() {{ @@ -904,7 +947,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(transportUpdateWriter).writeTransports(with(transports), with(any(long.class))); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.generateTransportUpdate(contactId, transportUpdateWriter); @@ -918,6 +962,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final Ack ack = context.mock(Ack.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -934,7 +979,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(Collections.singletonList(batchId1))); oneOf(database).removeLostBatch(txn, contactId, batchId1); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveAck(contactId, ack); @@ -947,6 +993,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final Batch batch = context.mock(Batch.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -965,7 +1012,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(batchId)); oneOf(database).addBatchToAck(txn, contactId, batchId); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveBatch(contactId, batch); @@ -978,6 +1026,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final Batch batch = context.mock(Batch.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -995,7 +1044,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(batchId)); oneOf(database).addBatchToAck(txn, contactId, batchId); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveBatch(contactId, batch); @@ -1009,6 +1059,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final Batch batch = context.mock(Batch.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -1027,7 +1078,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(batchId)); oneOf(database).addBatchToAck(txn, contactId, batchId); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveBatch(contactId, batch); @@ -1041,6 +1093,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final Batch batch = context.mock(Batch.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -1063,7 +1116,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(batchId)); oneOf(database).addBatchToAck(txn, contactId, batchId); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveBatch(contactId, batch); @@ -1076,6 +1130,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final Batch batch = context.mock(Batch.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -1107,7 +1162,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(batchId)); oneOf(database).addBatchToAck(txn, contactId, batchId); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveBatch(contactId, batch); @@ -1120,6 +1176,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final Batch batch = context.mock(Batch.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -1153,7 +1210,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(batchId)); oneOf(database).addBatchToAck(txn, contactId, batchId); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveBatch(contactId, batch); @@ -1175,6 +1233,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final Offer offer = context.mock(Offer.class); final RequestWriter requestWriter = context.mock(RequestWriter.class); context.checking(new Expectations() {{ @@ -1194,7 +1253,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(false)); // Not visible - request message # 2 oneOf(requestWriter).writeRequest(expectedRequest, 3); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveOffer(contactId, offer, requestWriter); @@ -1208,6 +1268,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final SubscriptionUpdate subscriptionUpdate = context.mock(SubscriptionUpdate.class); context.checking(new Expectations() {{ @@ -1224,7 +1285,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).setSubscriptions(txn, contactId, Collections.singletonMap(group, 0L), timestamp); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveSubscriptionUpdate(contactId, subscriptionUpdate); @@ -1238,6 +1300,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final TransportUpdate transportUpdate = context.mock(TransportUpdate.class); context.checking(new Expectations() {{ @@ -1254,7 +1317,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).setTransports(txn, contactId, transports, timestamp); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.receiveTransportUpdate(contactId, transportUpdate); @@ -1267,6 +1331,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) @@ -1288,7 +1353,8 @@ public abstract class DatabaseComponentTest extends TestCase { // The message was added, so the listener should be called oneOf(listener).eventOccurred(with(any(MessagesAddedEvent.class))); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addListener(listener); db.addLocalGroupMessage(message); @@ -1302,6 +1368,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -1316,7 +1383,8 @@ public abstract class DatabaseComponentTest extends TestCase { // The message was added, so the listener should be called oneOf(listener).eventOccurred(with(any(MessagesAddedEvent.class))); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addListener(listener); db.addLocalPrivateMessage(privateMessage, contactId); @@ -1331,6 +1399,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ // addLocalGroupMessage(message) @@ -1343,7 +1412,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(database).commitTransaction(txn); // The message was not added, so the listener should not be called }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addListener(listener); db.addLocalGroupMessage(message); @@ -1358,6 +1428,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); @@ -1370,7 +1441,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(false)); // The message was not added, so the listener should not be called }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addListener(listener); db.addLocalPrivateMessage(privateMessage, contactId); @@ -1387,6 +1459,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ oneOf(database).startTransaction(); @@ -1400,7 +1473,8 @@ public abstract class DatabaseComponentTest extends TestCase { oneOf(listener).eventOccurred(with(any( TransportAddedEvent.class))); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addListener(listener); db.setLocalProperties(transportId, properties); @@ -1417,6 +1491,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ oneOf(database).startTransaction(); @@ -1425,7 +1500,8 @@ public abstract class DatabaseComponentTest extends TestCase { will(returnValue(properties)); oneOf(database).commitTransaction(txn); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.addListener(listener); db.setLocalProperties(transportId, properties); @@ -1439,6 +1515,7 @@ public abstract class DatabaseComponentTest extends TestCase { @SuppressWarnings("unchecked") final Database<Object> database = context.mock(Database.class); final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ allowing(database).startTransaction(); will(returnValue(txn)); @@ -1448,7 +1525,8 @@ public abstract class DatabaseComponentTest extends TestCase { // setSeen(contactId, Collections.singletonList(messageId)) oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId); }}); - DatabaseComponent db = createDatabaseComponent(database, cleaner); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); db.setSeen(contactId, Collections.singletonList(messageId)); diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java index dcb4062ea55d3c953b676fcad1394eab5585c5fd..728346e155cadf68f3e4fe54aef9c8f449cc1cf7 100644 --- a/test/net/sf/briar/db/H2DatabaseTest.java +++ b/test/net/sf/briar/db/H2DatabaseTest.java @@ -44,6 +44,7 @@ import net.sf.briar.api.transport.ConnectionContextFactory; import net.sf.briar.api.transport.ConnectionWindow; import net.sf.briar.api.transport.ConnectionWindowFactory; import net.sf.briar.crypto.CryptoModule; +import net.sf.briar.lifecycle.LifecycleModule; import net.sf.briar.protocol.ProtocolModule; import net.sf.briar.protocol.writers.ProtocolWritersModule; import net.sf.briar.serial.SerialModule; @@ -105,10 +106,11 @@ public class H2DatabaseTest extends TestCase { } }; Injector i = Guice.createInjector(testModule, new CryptoModule(), - new DatabaseModule(), new ProtocolModule(), - new ProtocolWritersModule(), new SerialModule(), - new TransportBatchModule(), new TransportModule(), - new TransportStreamModule(), new TestDatabaseModule(testDir)); + new DatabaseModule(), new LifecycleModule(), + new ProtocolModule(), new ProtocolWritersModule(), + new SerialModule(), new TransportBatchModule(), + new TransportModule(), new TransportStreamModule(), + new TestDatabaseModule(testDir)); connectionContextFactory = i.getInstance(ConnectionContextFactory.class); connectionWindowFactory = i.getInstance(ConnectionWindowFactory.class); diff --git a/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java b/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fc2482c9fa300e2118bca22a6fca1660e3391b40 --- /dev/null +++ b/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java @@ -0,0 +1,29 @@ +package net.sf.briar.lifecycle; + +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; +import net.sf.briar.api.lifecycle.ShutdownManager; + +import org.junit.Test; + +public class ShutdownManagerImplTest extends TestCase { + + @Test + public void testAddAndRemove() { + ShutdownManager s = new ShutdownManagerImpl(); + Set<Integer> handles = new HashSet<Integer>(); + for(int i = 0; i < 100; i++) { + int handle = s.addShutdownHook(new Runnable() { + public void run() {} + }); + // The handles should all be distinct + assertTrue(handles.add(handle)); + } + // The hooks should be removable + for(int handle : handles) assertTrue(s.removeShutdownHook(handle)); + // The hooks should no longer be removable + for(int handle : handles) assertFalse(s.removeShutdownHook(handle)); + } +} diff --git a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java index 560fda09bd30529e2d45adf71cb8d1ea66931dbb..f0c5b8b765998a84c644b7ee1a4c82e13d67f8be 100644 --- a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java +++ b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java @@ -17,6 +17,7 @@ import net.sf.briar.api.crypto.CryptoComponent; import net.sf.briar.api.crypto.ErasableKey; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; +import net.sf.briar.api.lifecycle.ShutdownManager; import net.sf.briar.api.protocol.Transport; import net.sf.briar.api.protocol.TransportId; import net.sf.briar.api.protocol.TransportIndex; @@ -65,9 +66,11 @@ public class ConnectionRecogniserImplTest extends TestCase { public void testUnexpectedIv() throws Exception { Mockery context = new Mockery(); final DatabaseComponent db = context.mock(DatabaseComponent.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class))); // Initialise + oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(db).getLocalTransports(); will(returnValue(transports)); oneOf(db).getContacts(); @@ -77,8 +80,9 @@ public class ConnectionRecogniserImplTest extends TestCase { oneOf(db).getConnectionWindow(contactId, remoteIndex); will(returnValue(connectionWindow)); }}); - Executor e = new ImmediateExecutor(); - ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db, e); + Executor executor = new ImmediateExecutor(); + ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db, + executor, shutdown); c.acceptConnection(transportId, new byte[IV_LENGTH], new Callback() { public void connectionAccepted(ConnectionContext ctx) { @@ -112,9 +116,11 @@ public class ConnectionRecogniserImplTest extends TestCase { Mockery context = new Mockery(); final DatabaseComponent db = context.mock(DatabaseComponent.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); context.checking(new Expectations() {{ oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class))); // Initialise + oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(db).getLocalTransports(); will(returnValue(transports)); oneOf(db).getContacts(); @@ -127,8 +133,9 @@ public class ConnectionRecogniserImplTest extends TestCase { oneOf(db).setConnectionWindow(contactId, remoteIndex, connectionWindow); }}); - Executor e = new ImmediateExecutor(); - ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db, e); + Executor executor = new ImmediateExecutor(); + ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db, + executor, shutdown); // The IV should not be expected by the wrong transport TransportId wrong = new TransportId(TestUtils.getRandomId()); c.acceptConnection(wrong, encryptedIv, new Callback() { diff --git a/test/net/sf/briar/transport/ConnectionWriterTest.java b/test/net/sf/briar/transport/ConnectionWriterTest.java index 416943b3d001c3b6c434e07c8f38782569c79f3e..9e47c43b9418fa0ffc2ed28adc898f29241673d5 100644 --- a/test/net/sf/briar/transport/ConnectionWriterTest.java +++ b/test/net/sf/briar/transport/ConnectionWriterTest.java @@ -18,6 +18,7 @@ import net.sf.briar.api.transport.ConnectionWriter; import net.sf.briar.api.transport.ConnectionWriterFactory; import net.sf.briar.crypto.CryptoModule; import net.sf.briar.db.DatabaseModule; +import net.sf.briar.lifecycle.LifecycleModule; import net.sf.briar.protocol.ProtocolModule; import net.sf.briar.protocol.writers.ProtocolWritersModule; import net.sf.briar.serial.SerialModule; @@ -50,10 +51,11 @@ public class ConnectionWriterTest extends TestCase { } }; Injector i = Guice.createInjector(testModule, new CryptoModule(), - new DatabaseModule(), new ProtocolModule(), - new ProtocolWritersModule(), new SerialModule(), - new TestDatabaseModule(), new TransportBatchModule(), - new TransportModule(), new TransportStreamModule()); + new DatabaseModule(), new LifecycleModule(), + new ProtocolModule(), new ProtocolWritersModule(), + new SerialModule(), new TestDatabaseModule(), + new TransportBatchModule(), new TransportModule(), + new TransportStreamModule()); connectionContextFactory = i.getInstance(ConnectionContextFactory.class); connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class); diff --git a/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java b/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java index 6fc6ccf193bb710592c747755411cc26d3349f31..45ce4c504fe051c7b1334e6d6595d62c7e064862 100644 --- a/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java +++ b/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java @@ -40,6 +40,7 @@ import net.sf.briar.api.transport.ConnectionRecogniser.Callback; import net.sf.briar.api.transport.ConnectionWriterFactory; import net.sf.briar.crypto.CryptoModule; import net.sf.briar.db.DatabaseModule; +import net.sf.briar.lifecycle.LifecycleModule; import net.sf.briar.protocol.ProtocolModule; import net.sf.briar.protocol.writers.ProtocolWritersModule; import net.sf.briar.serial.SerialModule; @@ -81,32 +82,24 @@ public class BatchConnectionReadWriteTest extends TestCase { @Before public void setUp() { testDir.mkdirs(); - // Create Alice's injector - Module aliceTestModule = new AbstractModule() { - @Override - public void configure() { - bind(Executor.class).toInstance( - new ScheduledThreadPoolExecutor(5)); - } - }; - alice = Guice.createInjector(aliceTestModule, new CryptoModule(), - new DatabaseModule(), new ProtocolModule(), - new ProtocolWritersModule(), new SerialModule(), - new TestDatabaseModule(aliceDir), new TransportBatchModule(), - new TransportModule(), new TransportStreamModule()); - // Create Bob's injector - Module bobTestModule = new AbstractModule() { + alice = createInjector(aliceDir); + bob = createInjector(bobDir); + } + + private Injector createInjector(File dir) { + Module testModule = new AbstractModule() { @Override public void configure() { bind(Executor.class).toInstance( new ScheduledThreadPoolExecutor(5)); } }; - bob = Guice.createInjector(bobTestModule, new CryptoModule(), - new DatabaseModule(), new ProtocolModule(), - new ProtocolWritersModule(), new SerialModule(), - new TestDatabaseModule(bobDir), new TransportBatchModule(), - new TransportModule(), new TransportStreamModule()); + return Guice.createInjector(testModule, new CryptoModule(), + new DatabaseModule(), new LifecycleModule(), + new ProtocolModule(), new ProtocolWritersModule(), + new SerialModule(), new TestDatabaseModule(dir), + new TransportBatchModule(), new TransportModule(), + new TransportStreamModule()); } @Test