Help coderanch get a
new server
by contributing to the fundraiser
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Ron McLeod
  • Paul Clapham
  • Devaka Cooray
  • Liutauras Vilda
Sheriffs:
  • Jeanne Boyarsky
  • paul wheaton
  • Henry Wong
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Tim Moores
  • Carey Brown
  • Mikalai Zaikin
Bartenders:
  • Lou Hamers
  • Piet Souris
  • Frits Walraven

Unit testing on multi-threads access to one database

 
Greenhorn
Posts: 2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hello everyone,
I am working on a programming assignment, in which I am given a class that implements a simple database using a random access file. The class provides public synchronized methods such as getRecord(), add(), find(), modify(), delete() to access records in the database. I am asked to
design lock(), unlock() methods for locking/unlocking a record to maintain database integerity in multi-users environment.
The idea is if two database clients attempt to perform the sequence lock(), getRecord(), modify(), unlock() concurrently, then both modification attempts will be handled correctly. For example, if the database has 4 fields: record number, product name, price, and quantity in stock, the below statements would be typical in the client code to update an inventory after a sale.
/* requestedProduct and orderQuantity are input parameters */
...
int recordNumberFound = find(requestedProduct);
lock(recordNumberFound);
Record rec = getRecord(recordNumberFound);
if (rec.quantityInStock >= orderQuantity) {
Record newRec = new Record();
/* The following look cumbersome and the Record constructor should be able to take all the fields as parameters. The actual implementation is a little more complex than this, just to demonstrate there is process needed to create a new record. */
newRec.recordNumber = rec.recordNumber;
newRec.productName = rec.productName;
newRec.price = rec.price;
newRec.quantityInStock = rec.quantityInStock - orderQuantity;
modify(newRec); // this will overwrite the existing record in the database
unlock(recordNumberFound);
} else {
unlock(recordNumberFound);
throw new DatabaseException("Not enough stock to fulfil the order.");
}
Basically, lock() and unlock methods use a HashSet to traffic-control access to a record. lock()checks if the record is already in the HashSet, if so then call wait(), if not then adds it in. unlock()removes the record from the HashSet and call notifyAll(). So by puposely omitting the lock() method,one or more of the concurrent users can get pass the if-test before the record is modified, and the quantityInStock may be overly reduced to negative.
To unit test the above scenario, my TestCase's setUp() creates a test database by copying all the
records from the original database, thus creates a new random access file. In tearDown(), the random access file for the test database is deleted. I thought I could use JunitPerf's loadTest() method to simulate multi-users environment by multi-threads.
The first problem I went into was each thread tried to create a new database, which was not allowed by the database class supplied to me (it saw there was a random access file with the same name exist and threw an IOException), and it didn't exactly serve my test objective either because I tried to simulate multi-users accessing the same database.
After some investigation, I found out junit.extensions.TestSetup could force one time setUp()and tearDown(), and came up with something like below:
public class DataLockTest {
private static Data testDatabase;
private static final int MAX_USERS = 10;
public static Test suite() {
/*
1. Open the supplied database class
2. Copy every record from the original database to testDatabase
*/
// Pass the testDatabase to the test case contained in the DataLockTestHelper class
Test testCase = new DataLockTestHelper("testModifyWithNoLock", testDatabase);
Test loadTest = new LoadTest(testCase, MAX_USERS);
TestSuite suite = new TestSuite();
suite.addTest(loadTest);
// Anonymous TestSetup object for one time tearDown method
TestSetup wrapper = new TestSetup(suite) {
protected void tearDown() {
/*
1. Close testDatabases,
2. Delete the underlying random access file
...
*/
}
};
return wrapper;
}
public class DataLockTestHelper extends TestCase {
private Data dataSource;
private static final int RECORD_NO = 24;
// Each user uses the same data source provided by DataLockTest
public DataLockTestHelper(String name, Data source) {
super(name);
dataSource = source;
}
/**
* Test fixture is setup in DataLockTest.
*/
protected void setUp() {}
/**
* Test fixture is torn down in DataLockTest.
*/
protected void tearDown() {}
public void testModifyWithNoLock() throws DatabaseException {
Record record, newRecord;
try {
// comment out lock() method call ----> dataSource.lock(RECORD_NO);
record = dataSource.getRecord(RECORD_NO);
if (record.quantityInStock >= 3) {
newRecord = new Record();
newRecord.recordNumber = record.recordNumber;
newRecord.price = record.price;
/* the field has 10 original, so if running 10 threads concurrently, very like it will become
negative */
newRecord.quantityInStock = record.quantityInStock - 3;
dataSource.modify(newRecord);
}
// comment out unlock() method call ---> dataSource.unlock(RECORD_NO);
/* Test if the quantity in stock field becomes negative. */
dataSource.lock(RECORD_NO);
record = dataSource.getRecord(RECORD_NO);
int quantity = record.quantityInStock;
dataSource.unlock(RECORD_NO);
/* The test is successful if the field has been overly reduced to negative */
if (quantity < 0) {
assertTrue(true);
}
} catch(DatabaseException fail) {
fail("Shouldn't raise DatabaseException.");
}
}
}
Now the test is successful, however, I have very skeptical about it. I don't know if it's ok to pass the testDatabase to the test case like this, and would some of the concurrent threads simply leave the test case gracefully without doing anything if the condition "if (quantity < 0)" is not met.
I appreciate any comment, please help, thanks!
 
With a little knowledge, a cast iron skillet is non-stick and lasts a lifetime.
reply
    Bookmark Topic Watch Topic
  • New Topic