diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java index 38a7bf68daa2eed9b3b50ab19c6c49456c5c6b08..90caecf8c3f2b8d720a0f03eb44dffd46a494e3e 100644 --- a/api/net/sf/briar/api/db/DatabaseComponent.java +++ b/api/net/sf/briar/api/db/DatabaseComponent.java @@ -14,10 +14,8 @@ import net.sf.briar.api.protocol.Message; public interface DatabaseComponent { static final long MEGABYTES = 1024L * 1024L; - static final long GIGABYTES = 1024L * MEGABYTES; // FIXME: Some of these should be configurable - static final long MAX_DB_SIZE = 2L * GIGABYTES; static final long MIN_FREE_SPACE = 300L * MEGABYTES; static final long CRITICAL_FREE_SPACE = 100L * MEGABYTES; static final long MAX_BYTES_BETWEEN_SPACE_CHECKS = 5L * MEGABYTES; diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java index 7c3021d919abb7d99975f9da25c3e03de1cdc15e..59bf432a51bd1b5929d80204d731dc4c30847182 100644 --- a/components/net/sf/briar/db/Database.java +++ b/components/net/sf/briar/db/Database.java @@ -14,8 +14,8 @@ import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; /** - * A low-level interface to the database that is managed by a - * DatabaseComponent. Most operations take a transaction argument, which is + * A low-level interface to the database (DatabaseComponent provides a + * high-level interface). Most operations take a transaction argument, which is * obtained by calling startTransaction(). Every transaction must be * terminated by calling either abortTransaction() or commitTransaction(), * even if an exception is thrown. diff --git a/components/net/sf/briar/db/H2Database.java b/components/net/sf/briar/db/H2Database.java index 28ae4f6c70e86f5c63b129009fe7858f555447d5..9c6908a7dba8fb96791c0bb40d1082f459b47888 100644 --- a/components/net/sf/briar/db/H2Database.java +++ b/components/net/sf/briar/db/H2Database.java @@ -10,31 +10,32 @@ import java.util.logging.Level; import java.util.logging.Logger; import net.sf.briar.api.crypto.Password; -import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabasePassword; import net.sf.briar.api.db.DbException; import net.sf.briar.api.protocol.MessageFactory; -import net.sf.briar.util.FileUtils; import com.google.inject.Inject; +/** Contains all the H2-specific code for the database. */ class H2Database extends JdbcDatabase { private static final Logger LOG = Logger.getLogger(H2Database.class.getName()); - private final Password password; private final File home; + private final Password password; private final String url; + private final long maxSize; @Inject - H2Database(MessageFactory messageFactory, - @DatabasePassword Password password) { + H2Database(File dir, MessageFactory messageFactory, + @DatabasePassword Password password, long maxSize) { super(messageFactory, "BINARY(32)"); + home = new File(dir, "db"); this.password = password; - home = new File(FileUtils.getBriarDirectory(), "Data/db/db"); url = "jdbc:h2:split:" + home.getPath() + ";CIPHER=AES;DB_CLOSE_ON_EXIT=false"; + this.maxSize = maxSize; } public void open(boolean resume) throws DbException { @@ -54,7 +55,7 @@ class H2Database extends JdbcDatabase { File dir = home.getParentFile(); long free = dir.getFreeSpace(); long used = getDiskSpace(dir); - long quota = DatabaseComponent.MAX_DB_SIZE - used; + long quota = maxSize - used; long min = Math.min(free, quota); if(LOG.isLoggable(Level.FINE)) LOG.fine("Free space: " + min); return min; diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java index 2f861175147ac152b1fffbbd215e52c3be03dc3e..c848cafa2a8f6edbb516522b19b2b6c5979d9052 100644 --- a/components/net/sf/briar/db/JdbcDatabase.java +++ b/components/net/sf/briar/db/JdbcDatabase.java @@ -31,6 +31,10 @@ import net.sf.briar.api.protocol.MessageFactory; import net.sf.briar.api.protocol.MessageId; import net.sf.briar.util.FileUtils; +/** + * A generic database implementation that can be used with any JDBC-compatible + * database library. (Tested with H2, Derby and HSQLDB.) + */ abstract class JdbcDatabase implements Database<Connection> { private static final String CREATE_LOCAL_SUBSCRIPTIONS = @@ -945,7 +949,8 @@ abstract class JdbcDatabase implements Database<Connection> { + " ON messages.groupId = contactSubscriptions.groupId" + " JOIN statuses ON messages.messageId = statuses.messageId" + " WHERE contactSubscriptions.contactId = ?" - + " AND statuses.contactId = ? AND status = ?"; + + " AND statuses.contactId = ? AND status = ?" + + " AND sendability > ZERO()"; ps = txn.prepareStatement(sql); ps.setInt(1, c.getInt()); ps.setInt(2, c.getInt()); diff --git a/components/net/sf/briar/protocol/MessageImpl.java b/components/net/sf/briar/protocol/MessageImpl.java index 7f8c92c3936a17e250f3d7daceed2039375a2bea..0ae46a8221f77244cf2c3f602aed55f2cb5c3707 100644 --- a/components/net/sf/briar/protocol/MessageImpl.java +++ b/components/net/sf/briar/protocol/MessageImpl.java @@ -5,7 +5,7 @@ import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.MessageId; -class MessageImpl implements Message { +public class MessageImpl implements Message { private final MessageId id, parent; private final GroupId group; diff --git a/test/build.xml b/test/build.xml index 12f16f5501fb11b2c0f99c25fe5407ad101ec217..35e47c88d8326f360322ff91d52f7979b864a50c 100644 --- a/test/build.xml +++ b/test/build.xml @@ -13,6 +13,7 @@ <path refid='test-classes'/> <path refid='util-classes'/> </classpath> + <test name='net.sf.briar.db.H2DatabaseTest'/> <test name='net.sf.briar.i18n.FontManagerTest'/> <test name='net.sf.briar.i18n.I18nTest'/> <test name='net.sf.briar.invitation.InvitationWorkerTest'/> diff --git a/test/net/sf/briar/TestUtils.java b/test/net/sf/briar/TestUtils.java index 32b8198f59aba43a6351db70b4f11b15fdca06ab..552fbd427e7ab0fd6a0c82a7bb494f6063b0c48d 100644 --- a/test/net/sf/briar/TestUtils.java +++ b/test/net/sf/briar/TestUtils.java @@ -30,8 +30,9 @@ public class TestUtils { return testDir; } - public static void deleteTestDirectories() { - delete(new File("test.tmp")); + public static void deleteTestDirectory(File testDir) { + delete(testDir); + testDir.getParentFile().delete(); // Delete if empty } public static File getBuildDirectory() { diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java new file mode 100644 index 0000000000000000000000000000000000000000..00c029ffae837c0f5573c3fe0467d998704d747b --- /dev/null +++ b/test/net/sf/briar/db/H2DatabaseTest.java @@ -0,0 +1,620 @@ +package net.sf.briar.db; + +import java.io.File; +import java.sql.Connection; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import junit.framework.TestCase; +import net.sf.briar.TestUtils; +import net.sf.briar.api.crypto.Password; +import net.sf.briar.api.db.ContactId; +import net.sf.briar.api.db.DbException; +import net.sf.briar.api.db.Rating; +import net.sf.briar.api.db.Status; +import net.sf.briar.api.protocol.AuthorId; +import net.sf.briar.api.protocol.BatchId; +import net.sf.briar.api.protocol.GroupId; +import net.sf.briar.api.protocol.Message; +import net.sf.briar.api.protocol.MessageFactory; +import net.sf.briar.api.protocol.MessageId; +import net.sf.briar.protocol.MessageImpl; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class H2DatabaseTest extends TestCase { + + private static final int ONE_MEGABYTE = 1024 * 1024; + private static final int MAX_SIZE = 5 * ONE_MEGABYTE; + + private final File testDir = TestUtils.getTestDirectory(); + // The password has the format <file password> <space> <user password> + private final String passwordString = "foo bar"; + // Some bytes for test IDs + private final byte[] idBytes = new byte[32], idBytes1 = new byte[32]; + private final BatchId batchId; + private final ContactId contactId; + private final MessageId messageId; + private final GroupId groupId; + private final AuthorId authorId; + private final long timestamp = System.currentTimeMillis(); + private final int size = 1234; + private final byte[] body = new byte[size]; + private final Message message; + + public H2DatabaseTest() { + super(); + for(int i = 0; i < idBytes.length; i++) idBytes[i] = (byte) i; + for(int i = 0; i < idBytes1.length; i++) idBytes1[i] = (byte) (i + 1); + for(int i = 0; i < body.length; i++) body[i] = (byte) i; + batchId = new BatchId(idBytes); + contactId = new ContactId(123); + messageId = new MessageId(idBytes); + groupId = new GroupId(idBytes); + authorId = new AuthorId(idBytes); + message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId, + timestamp, body); + } + + @Before + public void setUp() { + testDir.mkdirs(); + } + + @Test + public void testPersistence() throws DbException { + MessageFactory messageFactory = new TestMessageFactory(); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Store some records + Connection txn = db.startTransaction(); + assertFalse(db.containsContact(txn, contactId)); + db.addContact(txn, contactId); + assertTrue(db.containsContact(txn, contactId)); + assertFalse(db.containsSubscription(txn, groupId)); + db.addSubscription(txn, groupId); + assertTrue(db.containsSubscription(txn, groupId)); + assertFalse(db.containsMessage(txn, messageId)); + db.addMessage(txn, message); + assertTrue(db.containsMessage(txn, messageId)); + db.commitTransaction(txn); + db.close(); + + // Reopen the database + db = open(true, messageFactory); + // Check that the records are still there + txn = db.startTransaction(); + assertTrue(db.containsContact(txn, contactId)); + assertTrue(db.containsSubscription(txn, groupId)); + assertTrue(db.containsMessage(txn, messageId)); + Message m1 = db.getMessage(txn, messageId); + assertEquals(messageId, m1.getId()); + assertEquals(MessageId.NONE, m1.getParent()); + assertEquals(groupId, m1.getGroup()); + assertEquals(authorId, m1.getAuthor()); + assertEquals(timestamp, m1.getTimestamp()); + assertEquals(size, m1.getSize()); + assertTrue(Arrays.equals(body, m1.getBody())); + // Delete the records + db.removeContact(txn, contactId); + db.removeMessage(txn, messageId); + db.removeSubscription(txn, groupId); + db.commitTransaction(txn); + db.close(); + + // Repoen the database + db = open(true, messageFactory); + // Check that the records are gone + txn = db.startTransaction(); + assertFalse(db.containsContact(txn, contactId)); + assertFalse(db.containsSubscription(txn, groupId)); + assertFalse(db.containsMessage(txn, messageId)); + db.commitTransaction(txn); + db.close(); + } + + @Test + public void testRatings() throws DbException { + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + Database<Connection> db = open(false, messageFactory); + Connection txn = db.startTransaction(); + // Unknown authors should be unrated + assertEquals(Rating.UNRATED, db.getRating(txn, authorId)); + // Store a rating + db.setRating(txn, authorId, Rating.GOOD); + db.commitTransaction(txn); + // Check that the rating was stored + txn = db.startTransaction(); + assertEquals(Rating.GOOD, db.getRating(txn, authorId)); + db.commitTransaction(txn); + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testUnsubscribingRemovesMessage() throws DbException { + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Subscribe to a group and store a message + Connection txn = db.startTransaction(); + db.addSubscription(txn, groupId); + db.addMessage(txn, message); + db.commitTransaction(txn); + + // Unsubscribing from the group should delete the message + txn = db.startTransaction(); + assertTrue(db.containsMessage(txn, messageId)); + db.removeSubscription(txn, groupId); + assertFalse(db.containsMessage(txn, messageId)); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testSendableMessagesMustBeSendable() throws DbException { + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Add a contact, subscribe to a group and store a message + Connection txn = db.startTransaction(); + db.addContact(txn, contactId); + db.addSubscription(txn, groupId); + db.addSubscription(txn, contactId, groupId); + db.addMessage(txn, message); + db.setStatus(txn, contactId, messageId, Status.NEW); + db.commitTransaction(txn); + + // The message should not be sendable + txn = db.startTransaction(); + assertEquals(0, db.getSendability(txn, messageId)); + Iterator<MessageId> it = + db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + // Changing the sendability to > 0 should make the message sendable + txn = db.startTransaction(); + db.setSendability(txn, messageId, 1); + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + db.commitTransaction(txn); + + // Changing the sendability to 0 should make the message unsendable + txn = db.startTransaction(); + db.setSendability(txn, messageId, 0); + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testSendableMessagesMustBeNew() throws DbException { + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Add a contact, subscribe to a group and store a message + Connection txn = db.startTransaction(); + db.addContact(txn, contactId); + db.addSubscription(txn, groupId); + db.addSubscription(txn, contactId, groupId); + db.addMessage(txn, message); + db.setSendability(txn, messageId, 1); + db.commitTransaction(txn); + + // The message has no status yet, so it should not be sendable + txn = db.startTransaction(); + Iterator<MessageId> it = + db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + // Changing the status to Status.NEW should make the message sendable + txn = db.startTransaction(); + db.setStatus(txn, contactId, messageId, Status.NEW); + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + db.commitTransaction(txn); + + // Changing the status to SENT should make the message unsendable + txn = db.startTransaction(); + db.setStatus(txn, contactId, messageId, Status.SENT); + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + // Changing the status to SEEN should also make the message unsendable + txn = db.startTransaction(); + db.setStatus(txn, contactId, messageId, Status.SEEN); + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testSendableMessagesMustBeSubscribed() throws DbException { + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Add a contact, subscribe to a group and store a message + Connection txn = db.startTransaction(); + db.addContact(txn, contactId); + db.addSubscription(txn, groupId); + db.addMessage(txn, message); + db.setSendability(txn, messageId, 1); + db.setStatus(txn, contactId, messageId, Status.NEW); + db.commitTransaction(txn); + + // The contact is not subscribed, so the message should not be sendable + txn = db.startTransaction(); + Iterator<MessageId> it = + db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + // The contact subscribing should make the message sendable + txn = db.startTransaction(); + db.addSubscription(txn, contactId, groupId); + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + db.commitTransaction(txn); + + // The contact unsubscribing should make the message unsendable + txn = db.startTransaction(); + db.clearSubscriptions(txn, contactId); + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testSendableMessagesMustFitCapacity() throws DbException { + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Add a contact, subscribe to a group and store a message + Connection txn = db.startTransaction(); + db.addContact(txn, contactId); + db.addSubscription(txn, groupId); + db.addSubscription(txn, contactId, groupId); + db.addMessage(txn, message); + db.setSendability(txn, messageId, 1); + db.setStatus(txn, contactId, messageId, Status.NEW); + db.commitTransaction(txn); + + // The message is too large to send + txn = db.startTransaction(); + Iterator<MessageId> it = + db.getSendableMessages(txn, contactId, size - 1).iterator(); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + // The message is just the right size to send + txn = db.startTransaction(); + it = db.getSendableMessages(txn, contactId, size).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testBatchesToAck() throws DbException { + BatchId batchId1 = new BatchId(idBytes1); + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Add a contact and some batches to ack + Connection txn = db.startTransaction(); + db.addContact(txn, contactId); + db.addBatchToAck(txn, contactId, batchId); + db.addBatchToAck(txn, contactId, batchId1); + db.commitTransaction(txn); + + // Both batch IDs should be returned + txn = db.startTransaction(); + Set<BatchId> acks = db.removeBatchesToAck(txn, contactId); + assertEquals(2, acks.size()); + assertTrue(acks.contains(batchId)); + assertTrue(acks.contains(batchId1)); + db.commitTransaction(txn); + + // Both batch IDs should have been removed + txn = db.startTransaction(); + acks = db.removeBatchesToAck(txn, contactId); + assertEquals(0, acks.size()); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testRemoveAckedBatch() throws DbException { + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Add a contact, subscribe to a group and store a message + Connection txn = db.startTransaction(); + db.addContact(txn, contactId); + db.addSubscription(txn, groupId); + db.addSubscription(txn, contactId, groupId); + db.addMessage(txn, message); + db.setSendability(txn, messageId, 1); + db.setStatus(txn, contactId, messageId, Status.NEW); + db.commitTransaction(txn); + + // Get the message and mark it as sent + txn = db.startTransaction(); + Iterator<MessageId> it = + db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + assertFalse(it.hasNext()); + db.setStatus(txn, contactId, messageId, Status.SENT); + db.addOutstandingBatch(txn, contactId, batchId, + Collections.singleton(messageId)); + db.commitTransaction(txn); + + // The message should no longer be sendable + txn = db.startTransaction(); + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + // Pretend that the batch was acked + db.removeAckedBatch(txn, contactId, batchId); + // The message still should not be sendable + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testRemoveLostBatch() throws DbException { + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Add a contact, subscribe to a group and store a message + Connection txn = db.startTransaction(); + db.addContact(txn, contactId); + db.addSubscription(txn, groupId); + db.addSubscription(txn, contactId, groupId); + db.addMessage(txn, message); + db.setSendability(txn, messageId, 1); + db.setStatus(txn, contactId, messageId, Status.NEW); + db.commitTransaction(txn); + + // Get the message and mark it as sent + txn = db.startTransaction(); + Iterator<MessageId> it = + db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + assertFalse(it.hasNext()); + db.setStatus(txn, contactId, messageId, Status.SENT); + db.addOutstandingBatch(txn, contactId, batchId, + Collections.singleton(messageId)); + db.commitTransaction(txn); + + // The message should no longer be sendable + txn = db.startTransaction(); + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertFalse(it.hasNext()); + // Pretend that the batch was lost + db.removeLostBatch(txn, contactId, batchId); + // The message should be sendable again + it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testGetMessagesByAuthor() throws DbException { + AuthorId authorId1 = new AuthorId(idBytes1); + MessageId messageId1 = new MessageId(idBytes1); + Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId, + authorId1, timestamp, body); + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Subscribe to a group and store two messages + Connection txn = db.startTransaction(); + db.addSubscription(txn, groupId); + db.addMessage(txn, message); + db.addMessage(txn, message1); + db.commitTransaction(txn); + + // Check that each message is retrievable via its author + txn = db.startTransaction(); + Iterator<MessageId> it = + db.getMessagesByAuthor(txn, authorId).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + assertFalse(it.hasNext()); + it = db.getMessagesByAuthor(txn, authorId1).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId1, it.next()); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testGetMessagesByParent() throws DbException { + MessageId parentId = new MessageId(idBytes1); + Message message1 = new MessageImpl(messageId, parentId, groupId, + authorId, timestamp, body); + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Subscribe to a group and store a message + Connection txn = db.startTransaction(); + db.addSubscription(txn, groupId); + db.addMessage(txn, message1); + db.commitTransaction(txn); + + // Check that the message is retrievable via its parent + txn = db.startTransaction(); + Iterator<MessageId> it = + db.getMessagesByParent(txn, parentId).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testGetOldMessages() throws DbException { + MessageId messageId1 = new MessageId(idBytes1); + Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId, + authorId, timestamp + 1000, body); + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Subscribe to a group and store two messages + Connection txn = db.startTransaction(); + db.addSubscription(txn, groupId); + db.addMessage(txn, message); + db.addMessage(txn, message1); + db.commitTransaction(txn); + + // Allowing enough capacity for one message should return the older one + txn = db.startTransaction(); + Iterator<MessageId> it = db.getOldMessages(txn, size).iterator(); + assertTrue(it.hasNext()); + assertEquals(messageId, it.next()); + assertFalse(it.hasNext()); + db.commitTransaction(txn); + + // Allowing enough capacity for both messages should return both + txn = db.startTransaction(); + Set<MessageId> ids = new HashSet<MessageId>(); + for(MessageId id : db.getOldMessages(txn, size * 2)) ids.add(id); + assertEquals(2, ids.size()); + assertTrue(ids.contains(messageId)); + assertTrue(ids.contains(messageId1)); + db.commitTransaction(txn); + + db.close(); + context.assertIsSatisfied(); + } + + @Test + public void testGetFreeSpace() throws DbException { + byte[] largeBody = new byte[ONE_MEGABYTE]; + for(int i = 0; i < largeBody.length; i++) largeBody[i] = (byte) i; + Message message1 = new MessageImpl(messageId, MessageId.NONE, groupId, + authorId, timestamp, largeBody); + Mockery context = new Mockery(); + MessageFactory messageFactory = context.mock(MessageFactory.class); + + // Create a new database + Database<Connection> db = open(false, messageFactory); + // Sanity check: there should be enough space on disk for this test + assertTrue(testDir.getFreeSpace() > MAX_SIZE); + // The free space should not be more than the allowed maximum size + long free = db.getFreeSpace(); + assertTrue(free <= MAX_SIZE); + assertTrue(free > 0); + // Storing a message should reduce the free space + Connection txn = db.startTransaction(); + db.addSubscription(txn, groupId); + db.addMessage(txn, message1); + db.commitTransaction(txn); + assertTrue(db.getFreeSpace() < free); + + db.close(); + context.assertIsSatisfied(); + } + + private Database<Connection> open(boolean resume, + MessageFactory messageFactory) throws DbException { + final char[] passwordArray = passwordString.toCharArray(); + Mockery context = new Mockery(); + final Password password = context.mock(Password.class); + context.checking(new Expectations() {{ + oneOf(password).getPassword(); + will(returnValue(passwordArray)); + }}); + Database<Connection> db = + new H2Database(testDir, messageFactory, password, MAX_SIZE); + db.open(resume); + context.assertIsSatisfied(); + // The password array should be cleared after use + assertTrue(Arrays.equals(new char[passwordString.length()], + passwordArray)); + return db; + } + + @After + public void tearDown() { + TestUtils.deleteTestDirectory(testDir); + } + + private static class TestMessageFactory implements MessageFactory { + + public Message createMessage(MessageId id, MessageId parent, + GroupId group, AuthorId author, long timestamp, byte[] body) { + return new MessageImpl(id, parent, group, author, timestamp, body); + } + } +} diff --git a/test/net/sf/briar/i18n/I18nTest.java b/test/net/sf/briar/i18n/I18nTest.java index 23e9c8fa562010319970f54ace154672f1c0e78e..1b0cd5e18f42126dc476da280dd964e6ee2a4720 100644 --- a/test/net/sf/briar/i18n/I18nTest.java +++ b/test/net/sf/briar/i18n/I18nTest.java @@ -97,6 +97,6 @@ public class I18nTest extends TestCase { public void tearDown() { TestUtils.delete(base); TestUtils.delete(french); - TestUtils.deleteTestDirectories(); + TestUtils.deleteTestDirectory(testDir); } } diff --git a/test/net/sf/briar/invitation/InvitationWorkerTest.java b/test/net/sf/briar/invitation/InvitationWorkerTest.java index 3090f71f67c48129e681dd08e52dfb6b1dcb109d..482e0ac93fa4eb280b915b97bae09484c1797563 100644 --- a/test/net/sf/briar/invitation/InvitationWorkerTest.java +++ b/test/net/sf/briar/invitation/InvitationWorkerTest.java @@ -148,6 +148,6 @@ public class InvitationWorkerTest extends TestCase { @After public void tearDown() { - TestUtils.deleteTestDirectories(); + TestUtils.deleteTestDirectory(testDir); } } diff --git a/test/net/sf/briar/setup/SetupWorkerTest.java b/test/net/sf/briar/setup/SetupWorkerTest.java index c597d9cf94784e1d701ddd4c475cc15cebe2081d..5300bc87646e19e940690be6e94f1ef7958b0bf9 100644 --- a/test/net/sf/briar/setup/SetupWorkerTest.java +++ b/test/net/sf/briar/setup/SetupWorkerTest.java @@ -166,6 +166,6 @@ public class SetupWorkerTest extends TestCase { @After public void tearDown() { - TestUtils.deleteTestDirectories(); + TestUtils.deleteTestDirectory(testDir); } } diff --git a/test/net/sf/briar/util/FileUtilsTest.java b/test/net/sf/briar/util/FileUtilsTest.java index 8874a306b8b69698104d12aac67dd7537bb7dbc5..70e14c0f2d01b291c087b9f60e32d72da06baf8f 100644 --- a/test/net/sf/briar/util/FileUtilsTest.java +++ b/test/net/sf/briar/util/FileUtilsTest.java @@ -160,6 +160,6 @@ public class FileUtilsTest extends TestCase { @After public void tearDown() { - TestUtils.deleteTestDirectories(); + TestUtils.deleteTestDirectory(testDir); } } diff --git a/test/net/sf/briar/util/ZipUtilsTest.java b/test/net/sf/briar/util/ZipUtilsTest.java index 613c87cd74cd6ff7a3ec84bf6a0bdfb4aff94b13..85f5e1259596a961169fdbbd335bc768b1e159a8 100644 --- a/test/net/sf/briar/util/ZipUtilsTest.java +++ b/test/net/sf/briar/util/ZipUtilsTest.java @@ -196,6 +196,6 @@ public class ZipUtilsTest extends TestCase { @After public void tearDown() { - TestUtils.deleteTestDirectories(); + TestUtils.deleteTestDirectory(testDir); } }