DatabaseComponent throws an exception instead of returning silently if a contact is removed during an operation involving that contact. More unit tests.

This commit is contained in:
akwizgran
2011-07-05 18:15:44 +01:00
parent 13b3d4cc03
commit 5d768a5718
14 changed files with 282 additions and 134 deletions

View File

@@ -21,7 +21,6 @@ public interface DatabaseComponent {
static final long MAX_BYTES_BETWEEN_SPACE_CHECKS = 5L * MEGABYTES; static final long MAX_BYTES_BETWEEN_SPACE_CHECKS = 5L * MEGABYTES;
static final long MAX_MS_BETWEEN_SPACE_CHECKS = 60L * 1000L; // 1 min static final long MAX_MS_BETWEEN_SPACE_CHECKS = 60L * 1000L; // 1 min
static final long BYTES_PER_SWEEP = 5L * MEGABYTES; static final long BYTES_PER_SWEEP = 5L * MEGABYTES;
static final int RETRANSMIT_THRESHOLD = 3;
/** /**
* Opens the database. * Opens the database.

View File

@@ -4,6 +4,10 @@ public class DbException extends Exception {
private static final long serialVersionUID = 3706581789209939441L; private static final long serialVersionUID = 3706581789209939441L;
public DbException() {
super();
}
public DbException(Throwable t) { public DbException(Throwable t) {
super(t); super(t);
} }

View File

@@ -0,0 +1,14 @@
package net.sf.briar.api.db;
/**
* Thrown when a database operation is attempted for a contact that is not in
* the database.
*/
public class NoSuchContactException extends DbException {
private static final long serialVersionUID = -7048538231308207386L;
public NoSuchContactException() {
super();
}
}

View File

@@ -32,6 +32,16 @@ import net.sf.briar.api.protocol.MessageId;
*/ */
interface Database<T> { interface Database<T> {
/**
* A batch sent to a contact is considered lost when this many bundles have
* been received from the contact since the batch was sent.
* <p>
* FIXME: Come up with a better retransmission scheme. This scheme doesn't
* cope well with transports that have high latency but send bundles
* frequently.
*/
static final int RETRANSMIT_THRESHOLD = 3;
/** /**
* Opens the database. * Opens the database.
* @param resume True to reopen an existing database, false to create a * @param resume True to reopen an existing database, false to create a

View File

@@ -18,7 +18,6 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sf.briar.api.db.ContactId; import net.sf.briar.api.db.ContactId;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.Rating; import net.sf.briar.api.db.Rating;
import net.sf.briar.api.db.Status; import net.sf.briar.api.db.Status;
@@ -527,7 +526,7 @@ abstract class JdbcDatabase implements Database<Connection> {
rs.close(); rs.close();
ps.close(); ps.close();
Set<BatchId> lost; Set<BatchId> lost;
if(received == DatabaseComponent.RETRANSMIT_THRESHOLD) { if(received == RETRANSMIT_THRESHOLD) {
// Expire batches related to the oldest received bundle // Expire batches related to the oldest received bundle
assert oldestBundle != null; assert oldestBundle != null;
lost = findLostBatches(txn, c, oldestBundle); lost = findLostBatches(txn, c, oldestBundle);

View File

@@ -7,8 +7,9 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.ContactId; import net.sf.briar.api.db.ContactId;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.Rating; import net.sf.briar.api.db.Rating;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Batch;
@@ -120,7 +121,10 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
// unsubscribed from the group // unsubscribed from the group
if(db.containsSubscription(txn, m.getGroup())) { if(db.containsSubscription(txn, m.getGroup())) {
boolean added = storeMessage(txn, m, null); boolean added = storeMessage(txn, m, null);
assert added; if(!added) {
if(LOG.isLoggable(Level.FINE))
LOG.fine("Duplicate local message");
}
} else { } else {
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Not subscribed"); LOG.fine("Not subscribed");
@@ -177,7 +181,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
// Ack all batches received from c // Ack all batches received from c
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
messageStatusLock.writeLock().lock(); messageStatusLock.writeLock().lock();
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
@@ -203,7 +207,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
// Add a list of subscriptions // Add a list of subscriptions
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
subscriptionLock.readLock().lock(); subscriptionLock.readLock().lock();
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
@@ -246,7 +250,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
private Batch fillBatch(ContactId c, long capacity) throws DbException { private Batch fillBatch(ContactId c, long capacity) throws DbException {
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) return null; if(!containsContact(c)) throw new NoSuchContactException();
messageLock.readLock().lock(); messageLock.readLock().lock();
try { try {
Set<MessageId> sent; Set<MessageId> sent;
@@ -344,7 +348,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
// Mark all messages in acked batches as seen // Mark all messages in acked batches as seen
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
messageLock.readLock().lock(); messageLock.readLock().lock();
try { try {
messageStatusLock.writeLock().lock(); messageStatusLock.writeLock().lock();
@@ -375,7 +379,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
// Update the contact's subscriptions // Update the contact's subscriptions
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
messageStatusLock.writeLock().lock(); messageStatusLock.writeLock().lock();
try { try {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
@@ -406,7 +410,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
waitForPermissionToWrite(); waitForPermissionToWrite();
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
messageLock.writeLock().lock(); messageLock.writeLock().lock();
try { try {
messageStatusLock.writeLock().lock(); messageStatusLock.writeLock().lock();
@@ -451,7 +455,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
Set<BatchId> lost; Set<BatchId> lost;
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
messageLock.readLock().lock(); messageLock.readLock().lock();
try { try {
messageStatusLock.writeLock().lock(); messageStatusLock.writeLock().lock();
@@ -476,7 +480,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
for(BatchId batch : lost) { for(BatchId batch : lost) {
contactLock.readLock().lock(); contactLock.readLock().lock();
try { try {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
messageLock.readLock().lock(); messageLock.readLock().lock();
try { try {
messageStatusLock.writeLock().lock(); messageStatusLock.writeLock().lock();

View File

@@ -8,6 +8,7 @@ import java.util.logging.Logger;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.ContactId; import net.sf.briar.api.db.ContactId;
import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.Rating; import net.sf.briar.api.db.Rating;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Batch;
@@ -89,7 +90,10 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
// unsubscribed from the group // unsubscribed from the group
if(db.containsSubscription(txn, m.getGroup())) { if(db.containsSubscription(txn, m.getGroup())) {
boolean added = storeMessage(txn, m, null); boolean added = storeMessage(txn, m, null);
assert added; if(!added) {
if(LOG.isLoggable(Level.FINE))
LOG.fine("Duplicate local message");
}
} else { } else {
if(LOG.isLoggable(Level.FINE)) if(LOG.isLoggable(Level.FINE))
LOG.fine("Not subscribed"); LOG.fine("Not subscribed");
@@ -128,7 +132,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c); if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c);
// Ack all batches received from c // Ack all batches received from c
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
@@ -148,7 +152,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
// Add a list of subscriptions // Add a list of subscriptions
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
synchronized(subscriptionLock) { synchronized(subscriptionLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
@@ -185,7 +189,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
private Batch fillBatch(ContactId c, long capacity) throws DbException { private Batch fillBatch(ContactId c, long capacity) throws DbException {
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) return null; if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageLock) { synchronized(messageLock) {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
@@ -254,7 +258,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
+ b.getSize() + " bytes"); + b.getSize() + " bytes");
// Mark all messages in acked batches as seen // Mark all messages in acked batches as seen
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageLock) { synchronized(messageLock) {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
int acks = 0; int acks = 0;
@@ -276,7 +280,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
// Update the contact's subscriptions // Update the contact's subscriptions
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
try { try {
@@ -301,7 +305,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
batches++; batches++;
waitForPermissionToWrite(); waitForPermissionToWrite();
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageLock) { synchronized(messageLock) {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
synchronized(subscriptionLock) { synchronized(subscriptionLock) {
@@ -334,7 +338,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
// Find any lost batches that need to be retransmitted // Find any lost batches that need to be retransmitted
Set<BatchId> lost; Set<BatchId> lost;
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageLock) { synchronized(messageLock) {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();
@@ -350,7 +354,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
} }
for(BatchId batch : lost) { for(BatchId batch : lost) {
synchronized(contactLock) { synchronized(contactLock) {
if(!containsContact(c)) return; if(!containsContact(c)) throw new NoSuchContactException();
synchronized(messageLock) { synchronized(messageLock) {
synchronized(messageStatusLock) { synchronized(messageStatusLock) {
Txn txn = db.startTransaction(); Txn txn = db.startTransaction();

View File

@@ -15,6 +15,8 @@
</classpath> </classpath>
<test name='net.sf.briar.db.DatabaseCleanerImplTest'/> <test name='net.sf.briar.db.DatabaseCleanerImplTest'/>
<test name='net.sf.briar.db.H2DatabaseTest'/> <test name='net.sf.briar.db.H2DatabaseTest'/>
<test name='net.sf.briar.db.ReadWriteLockDatabaseComponentTest'/>
<test name='net.sf.briar.db.SynchronizedDatabaseComponentTest'/>
<test name='net.sf.briar.i18n.FontManagerTest'/> <test name='net.sf.briar.i18n.FontManagerTest'/>
<test name='net.sf.briar.i18n.I18nTest'/> <test name='net.sf.briar.i18n.I18nTest'/>
<test name='net.sf.briar.invitation.InvitationWorkerTest'/> <test name='net.sf.briar.invitation.InvitationWorkerTest'/>

View File

@@ -4,12 +4,16 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import net.sf.briar.api.protocol.UniqueId;
public class TestUtils { public class TestUtils {
private static final AtomicInteger nextTestDir = private static final AtomicInteger nextTestDir =
new AtomicInteger((int) (Math.random() * 1000 * 1000)); new AtomicInteger((int) (Math.random() * 1000 * 1000));
private static final Random random = new Random();
public static void delete(File f) { public static void delete(File f) {
if(f.isDirectory()) for(File child : f.listFiles()) delete(child); if(f.isDirectory()) for(File child : f.listFiles()) delete(child);
@@ -42,4 +46,10 @@ public class TestUtils {
if(bin.exists() && bin.isDirectory()) return bin; if(bin.exists() && bin.isDirectory()) return bin;
throw new RuntimeException("Could not find build directory"); throw new RuntimeException("Could not find build directory");
} }
public static byte[] getRandomId() {
byte[] b = new byte[UniqueId.LENGTH];
random.nextBytes(b);
return b;
}
} }

View File

@@ -0,0 +1,140 @@
package net.sf.briar.db;
import static net.sf.briar.api.db.DatabaseComponent.BYTES_PER_SWEEP;
import static net.sf.briar.api.db.DatabaseComponent.MIN_FREE_SPACE;
import java.util.Collections;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.MessageId;
import net.sf.briar.db.DatabaseCleaner.Callback;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import com.google.inject.Provider;
/**
* Tests that use the DatabaseCleaner.Callback interface of
* DatabaseComponentImpl.
*/
public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
protected abstract <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
Database<T> database, DatabaseCleaner cleaner,
Provider<Batch> batchProvider);
@Test
public void testNotCleanedIfEnoughFreeSpace() throws DbException {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner,
batchProvider);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Test
public void testCleanedIfNotEnoughFreeSpace() throws DbException {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE - 1));
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
will(returnValue(Collections.emptySet()));
oneOf(database).commitTransaction(txn);
// As if by magic, some free space has appeared
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner,
batchProvider);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Test
public void testExpiringUnsendableMessageDoesNotTriggerBackwardInclusion()
throws DbException {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE - 1));
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
will(returnValue(Collections.singleton(messageId)));
oneOf(database).getSendability(txn, messageId);
will(returnValue(0));
oneOf(database).removeMessage(txn, messageId);
oneOf(database).commitTransaction(txn);
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner,
batchProvider);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
@Test
public void testExpiringSendableMessageTriggersBackwardInclusion()
throws DbException {
Mockery context = new Mockery();
@SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE - 1));
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).getOldMessages(txn, BYTES_PER_SWEEP);
will(returnValue(Collections.singleton(messageId)));
oneOf(database).getSendability(txn, messageId);
will(returnValue(1));
oneOf(database).getParent(txn, messageId);
will(returnValue(MessageId.NONE));
oneOf(database).removeMessage(txn, messageId);
oneOf(database).commitTransaction(txn);
oneOf(database).getFreeSpace();
will(returnValue(MIN_FREE_SPACE));
}});
Callback db = createDatabaseComponentImpl(database, cleaner,
batchProvider);
db.checkFreeSpaceAndClean();
context.assertIsSatisfied();
}
}

View File

@@ -1,8 +1,6 @@
package net.sf.briar.db; package net.sf.briar.db;
import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.Random;
import java.util.Set; import java.util.Set;
import junit.framework.TestCase; import junit.framework.TestCase;
@@ -10,10 +8,12 @@ import net.sf.briar.TestUtils;
import net.sf.briar.api.db.ContactId; import net.sf.briar.api.db.ContactId;
import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.Rating; import net.sf.briar.api.db.Rating;
import net.sf.briar.api.db.Status; import net.sf.briar.api.db.Status;
import net.sf.briar.api.protocol.AuthorId; import net.sf.briar.api.protocol.AuthorId;
import net.sf.briar.api.protocol.Batch; import net.sf.briar.api.protocol.Batch;
import net.sf.briar.api.protocol.Bundle;
import net.sf.briar.api.protocol.GroupId; import net.sf.briar.api.protocol.GroupId;
import net.sf.briar.api.protocol.Message; import net.sf.briar.api.protocol.Message;
import net.sf.briar.api.protocol.MessageId; import net.sf.briar.api.protocol.MessageId;
@@ -21,37 +21,32 @@ import net.sf.briar.protocol.MessageImpl;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.junit.After;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import com.google.inject.Provider; import com.google.inject.Provider;
public abstract class DatabaseComponentTest extends TestCase { public abstract class DatabaseComponentTest extends TestCase {
private final File testDir = TestUtils.getTestDirectory(); protected final Object txn = new Object();
private final Random random = new Random(); protected final AuthorId authorId;
private final AuthorId authorId; protected final ContactId contactId;
private final ContactId contactId; protected final GroupId groupId;
private final GroupId groupId; protected final MessageId messageId, parentId;
private final MessageId messageId, parentId;
private final long timestamp; private final long timestamp;
private final int size; private final int size;
private final byte[] body; private final byte[] body;
private final Message message; private final Message message;
private final Object txn = new Object();
public DatabaseComponentTest() { public DatabaseComponentTest() {
super(); super();
authorId = new AuthorId(getRandomId()); authorId = new AuthorId(TestUtils.getRandomId());
contactId = new ContactId(123); contactId = new ContactId(123);
groupId = new GroupId(getRandomId()); groupId = new GroupId(TestUtils.getRandomId());
messageId = new MessageId(getRandomId()); messageId = new MessageId(TestUtils.getRandomId());
parentId = new MessageId(getRandomId()); parentId = new MessageId(TestUtils.getRandomId());
timestamp = System.currentTimeMillis(); timestamp = System.currentTimeMillis();
size = 1234; size = 1234;
body = new byte[size]; body = new byte[size];
random.nextBytes(body);
message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId, message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId,
timestamp, body); timestamp, body);
} }
@@ -60,11 +55,6 @@ public abstract class DatabaseComponentTest extends TestCase {
Database<T> database, DatabaseCleaner cleaner, Database<T> database, DatabaseCleaner cleaner,
Provider<Batch> batchProvider); Provider<Batch> batchProvider);
@Before
public void setUp() {
testDir.mkdirs();
}
@Test @Test
public void testSimpleCalls() throws DbException { public void testSimpleCalls() throws DbException {
final Set<GroupId> subs = Collections.singleton(groupId); final Set<GroupId> subs = Collections.singleton(groupId);
@@ -137,8 +127,6 @@ public abstract class DatabaseComponentTest extends TestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class); final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).open(false);
oneOf(cleaner).startCleaning();
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -153,15 +141,11 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).getParent(txn, messageId); oneOf(database).getParent(txn, messageId);
will(returnValue(MessageId.NONE)); will(returnValue(MessageId.NONE));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(cleaner).stopCleaning();
oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider); batchProvider);
db.open(false);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -176,8 +160,6 @@ public abstract class DatabaseComponentTest extends TestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class); final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).open(false);
oneOf(cleaner).startCleaning();
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -195,22 +177,18 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).containsMessage(txn, parentId); oneOf(database).containsMessage(txn, parentId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(cleaner).stopCleaning();
oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider); batchProvider);
db.open(false);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@Test @Test
public void testChangingGroupsStopsBackwardInclusion() throws DbException { public void testChangingGroupsStopsBackwardInclusion() throws DbException {
final GroupId groupId1 = new GroupId(getRandomId()); final GroupId groupId1 = new GroupId(TestUtils.getRandomId());
final Set<MessageId> messages = Collections.singleton(messageId); final Set<MessageId> messages = Collections.singleton(messageId);
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -219,8 +197,6 @@ public abstract class DatabaseComponentTest extends TestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class); final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).open(false);
oneOf(cleaner).startCleaning();
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -242,15 +218,11 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).getGroup(txn, parentId); oneOf(database).getGroup(txn, parentId);
will(returnValue(groupId1)); will(returnValue(groupId1));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(cleaner).stopCleaning();
oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider); batchProvider);
db.open(false);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -266,8 +238,6 @@ public abstract class DatabaseComponentTest extends TestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class); final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).open(false);
oneOf(cleaner).startCleaning();
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -292,15 +262,11 @@ public abstract class DatabaseComponentTest extends TestCase {
will(returnValue(1)); will(returnValue(1));
oneOf(database).setSendability(txn, parentId, 2); oneOf(database).setSendability(txn, parentId, 2);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(cleaner).stopCleaning();
oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider); batchProvider);
db.open(false);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -316,8 +282,6 @@ public abstract class DatabaseComponentTest extends TestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class); final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).open(false);
oneOf(cleaner).startCleaning();
// setRating(Rating.GOOD) // setRating(Rating.GOOD)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -344,15 +308,11 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).getParent(txn, parentId); oneOf(database).getParent(txn, parentId);
will(returnValue(MessageId.NONE)); will(returnValue(MessageId.NONE));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(cleaner).stopCleaning();
oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider); batchProvider);
db.open(false);
db.setRating(authorId, Rating.GOOD); db.setRating(authorId, Rating.GOOD);
db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -367,23 +327,17 @@ public abstract class DatabaseComponentTest extends TestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class); final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).open(false);
oneOf(cleaner).startCleaning();
// addLocallyGeneratedMessage(message) // addLocallyGeneratedMessage(message)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsSubscription(txn, groupId); oneOf(database).containsSubscription(txn, groupId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(cleaner).stopCleaning();
oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider); batchProvider);
db.open(false);
db.addLocallyGeneratedMessage(message); db.addLocallyGeneratedMessage(message);
db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -397,8 +351,6 @@ public abstract class DatabaseComponentTest extends TestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class); final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).open(false);
oneOf(cleaner).startCleaning();
// addLocallyGeneratedMessage(message) // addLocallyGeneratedMessage(message)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -407,15 +359,11 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).addMessage(txn, message); oneOf(database).addMessage(txn, message);
will(returnValue(false)); will(returnValue(false));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(cleaner).stopCleaning();
oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider); batchProvider);
db.open(false);
db.addLocallyGeneratedMessage(message); db.addLocallyGeneratedMessage(message);
db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -429,8 +377,6 @@ public abstract class DatabaseComponentTest extends TestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class); final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).open(false);
oneOf(cleaner).startCleaning();
// addLocallyGeneratedMessage(message) // addLocallyGeneratedMessage(message)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -448,15 +394,11 @@ public abstract class DatabaseComponentTest extends TestCase {
will(returnValue(0)); will(returnValue(0));
oneOf(database).setSendability(txn, messageId, 0); oneOf(database).setSendability(txn, messageId, 0);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(cleaner).stopCleaning();
oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider); batchProvider);
db.open(false);
db.addLocallyGeneratedMessage(message); db.addLocallyGeneratedMessage(message);
db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -471,8 +413,6 @@ public abstract class DatabaseComponentTest extends TestCase {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class); final Provider<Batch> batchProvider = context.mock(Provider.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).open(false);
oneOf(cleaner).startCleaning();
// addLocallyGeneratedMessage(message) // addLocallyGeneratedMessage(message)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -493,27 +433,40 @@ public abstract class DatabaseComponentTest extends TestCase {
oneOf(database).getParent(txn, messageId); oneOf(database).getParent(txn, messageId);
will(returnValue(MessageId.NONE)); will(returnValue(MessageId.NONE));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(cleaner).stopCleaning();
oneOf(database).close();
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider); batchProvider);
db.open(false);
db.addLocallyGeneratedMessage(message); db.addLocallyGeneratedMessage(message);
db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
private byte[] getRandomId() { @Test
byte[] b = new byte[32]; public void testGenerateBundleThrowsExceptionIfContactIsMissing()
random.nextBytes(b); throws DbException {
return b; Mockery context = new Mockery();
} @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class);
final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
@SuppressWarnings("unchecked")
final Provider<Batch> batchProvider = context.mock(Provider.class);
final Bundle bundle = context.mock(Bundle.class);
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(false));
oneOf(database).commitTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, cleaner,
batchProvider);
@After try {
public void tearDown() { db.generateBundle(contactId, bundle);
TestUtils.deleteTestDirectory(testDir); assertTrue(false);
} catch(NoSuchContactException expected) {}
context.assertIsSatisfied();
} }
} }

View File

@@ -53,11 +53,11 @@ public class H2DatabaseTest extends TestCase {
public H2DatabaseTest() { public H2DatabaseTest() {
super(); super();
authorId = new AuthorId(getRandomId()); authorId = new AuthorId(TestUtils.getRandomId());
batchId = new BatchId(getRandomId()); batchId = new BatchId(TestUtils.getRandomId());
contactId = new ContactId(123); contactId = new ContactId(123);
groupId = new GroupId(getRandomId()); groupId = new GroupId(TestUtils.getRandomId());
messageId = new MessageId(getRandomId()); messageId = new MessageId(TestUtils.getRandomId());
timestamp = System.currentTimeMillis(); timestamp = System.currentTimeMillis();
size = 1234; size = 1234;
body = new byte[size]; body = new byte[size];
@@ -338,7 +338,7 @@ public class H2DatabaseTest extends TestCase {
@Test @Test
public void testBatchesToAck() throws DbException { public void testBatchesToAck() throws DbException {
BatchId batchId1 = new BatchId(getRandomId()); BatchId batchId1 = new BatchId(TestUtils.getRandomId());
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
@@ -461,13 +461,13 @@ public class H2DatabaseTest extends TestCase {
@Test @Test
public void testRetransmission() throws DbException { public void testRetransmission() throws DbException {
BundleId bundleId = new BundleId(getRandomId()); BundleId bundleId = new BundleId(TestUtils.getRandomId());
BundleId bundleId1 = new BundleId(getRandomId()); BundleId bundleId1 = new BundleId(TestUtils.getRandomId());
BundleId bundleId2 = new BundleId(getRandomId()); BundleId bundleId2 = new BundleId(TestUtils.getRandomId());
BundleId bundleId3 = new BundleId(getRandomId()); BundleId bundleId3 = new BundleId(TestUtils.getRandomId());
BundleId bundleId4 = new BundleId(getRandomId()); BundleId bundleId4 = new BundleId(TestUtils.getRandomId());
BatchId batchId1 = new BatchId(getRandomId()); BatchId batchId1 = new BatchId(TestUtils.getRandomId());
BatchId batchId2 = new BatchId(getRandomId()); BatchId batchId2 = new BatchId(TestUtils.getRandomId());
Set<MessageId> empty = Collections.emptySet(); Set<MessageId> empty = Collections.emptySet();
Mockery context = new Mockery(); Mockery context = new Mockery();
MessageFactory messageFactory = context.mock(MessageFactory.class); MessageFactory messageFactory = context.mock(MessageFactory.class);
@@ -508,8 +508,8 @@ public class H2DatabaseTest extends TestCase {
@Test @Test
public void testGetMessagesByAuthor() throws DbException { public void testGetMessagesByAuthor() throws DbException {
AuthorId authorId1 = new AuthorId(getRandomId()); AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
MessageId messageId1 = new MessageId(getRandomId()); MessageId messageId1 = new MessageId(TestUtils.getRandomId());
Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId, Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId,
authorId1, timestamp, body); authorId1, timestamp, body);
Mockery context = new Mockery(); Mockery context = new Mockery();
@@ -543,10 +543,10 @@ public class H2DatabaseTest extends TestCase {
@Test @Test
public void testGetNumberOfSendableChildren() throws DbException { public void testGetNumberOfSendableChildren() throws DbException {
MessageId childId1 = new MessageId(getRandomId()); MessageId childId1 = new MessageId(TestUtils.getRandomId());
MessageId childId2 = new MessageId(getRandomId()); MessageId childId2 = new MessageId(TestUtils.getRandomId());
MessageId childId3 = new MessageId(getRandomId()); MessageId childId3 = new MessageId(TestUtils.getRandomId());
GroupId groupId1 = new GroupId(getRandomId()); GroupId groupId1 = new GroupId(TestUtils.getRandomId());
Message child1 = new MessageImpl(childId1, messageId, groupId, Message child1 = new MessageImpl(childId1, messageId, groupId,
authorId, timestamp, body); authorId, timestamp, body);
Message child2 = new MessageImpl(childId2, messageId, groupId, Message child2 = new MessageImpl(childId2, messageId, groupId,
@@ -588,7 +588,7 @@ public class H2DatabaseTest extends TestCase {
@Test @Test
public void testGetOldMessages() throws DbException { public void testGetOldMessages() throws DbException {
MessageId messageId1 = new MessageId(getRandomId()); MessageId messageId1 = new MessageId(TestUtils.getRandomId());
Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId, Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId,
authorId, timestamp + 1000, body); authorId, timestamp + 1000, body);
Mockery context = new Mockery(); Mockery context = new Mockery();
@@ -758,12 +758,6 @@ public class H2DatabaseTest extends TestCase {
TestUtils.deleteTestDirectory(testDir); TestUtils.deleteTestDirectory(testDir);
} }
private byte[] getRandomId() {
byte[] b = new byte[32];
random.nextBytes(b);
return b;
}
private static class TestMessageFactory implements MessageFactory { private static class TestMessageFactory implements MessageFactory {
public Message createMessage(MessageId id, MessageId parent, public Message createMessage(MessageId id, MessageId parent,

View File

@@ -5,12 +5,20 @@ import net.sf.briar.api.protocol.Batch;
import com.google.inject.Provider; import com.google.inject.Provider;
public class ReadWriteLockDatabaseComponentTest extends DatabaseComponentTest { public class ReadWriteLockDatabaseComponentTest
extends DatabaseComponentImplTest {
@Override @Override
protected <T> DatabaseComponent createDatabaseComponent( protected <T> DatabaseComponent createDatabaseComponent(
Database<T> database, DatabaseCleaner cleaner, Database<T> database, DatabaseCleaner cleaner,
Provider<Batch> batchProvider) { Provider<Batch> batchProvider) {
return createDatabaseComponentImpl(database, cleaner, batchProvider);
}
@Override
protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
Database<T> database, DatabaseCleaner cleaner,
Provider<Batch> batchProvider) {
return new ReadWriteLockDatabaseComponent<T>(database, cleaner, return new ReadWriteLockDatabaseComponent<T>(database, cleaner,
batchProvider); batchProvider);
} }

View File

@@ -5,14 +5,21 @@ import net.sf.briar.api.protocol.Batch;
import com.google.inject.Provider; import com.google.inject.Provider;
public class SynchronizedDatabaseComponentTest extends DatabaseComponentTest { public class SynchronizedDatabaseComponentTest
extends DatabaseComponentImplTest {
@Override @Override
protected <T> DatabaseComponent createDatabaseComponent( protected <T> DatabaseComponent createDatabaseComponent(
Database<T> database, DatabaseCleaner cleaner, Database<T> database, DatabaseCleaner cleaner,
Provider<Batch> batchProvider) { Provider<Batch> batchProvider) {
return createDatabaseComponentImpl(database, cleaner, batchProvider);
}
@Override
protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
Database<T> database, DatabaseCleaner cleaner,
Provider<Batch> batchProvider) {
return new SynchronizedDatabaseComponent<T>(database, cleaner, return new SynchronizedDatabaseComponent<T>(database, cleaner,
batchProvider); batchProvider);
} }
} }