/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.test.marathon.test;
import java.rmi.RemoteException;
import java.rmi.ServerException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.TransactionRolledbackException;
import org.jboss.test.banknew.interfaces.AccountData;
import org.jboss.test.banknew.interfaces.AccountSession;
import org.jboss.test.banknew.interfaces.AccountSessionHome;
import org.jboss.test.banknew.interfaces.BankData;
import org.jboss.test.banknew.interfaces.BankSession;
import org.jboss.test.banknew.interfaces.BankSessionHome;
import org.jboss.test.banknew.interfaces.Constants;
import org.jboss.test.banknew.interfaces.CustomerData;
import org.jboss.test.banknew.interfaces.CustomerSession;
import org.jboss.test.banknew.interfaces.CustomerSessionHome;
import org.jboss.test.banknew.interfaces.TellerSession;
import org.jboss.test.banknew.interfaces.TellerSessionHome;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.util.deadlock.ApplicationDeadlockException;
import org.jboss.test.JBossTestCase;
import org.jboss.logging.Logger;
/**
* Marathon test case to test JBoss under moderate utilization for
* a long time (hours or days) to see if there is a memory leak or
* other long time exceptions
*
* @see <related>
* @author Author: Andreas Schaefer
* @version $Revision: 81036 $
*/
public class BankMarathonTestCase
extends JBossTestCase
{
// Constants -----------------------------------------------------
public static final int DEFAULT_DURATION = Constants.ONE_DAY; // 1 Day (24 hours)
private static final int INITIAL_NUMBER_OF_CUSTOMERS = 100; // Create this many customer in setup Bank
// Attributes ----------------------------------------------------
private volatile int mCount;
private volatile Exception mException;
private volatile boolean mExit = false;
private Context mContext;
private BankSession mBankSession = null;
public int mCustomerCount = INITIAL_NUMBER_OF_CUSTOMERS;
// Static --------------------------------------------------------
public static Test suite() throws Exception {
return getDeploySetup( BankMarathonTestCase.class, "banknew.jar" );
}
// Constructors --------------------------------------------------
public BankMarathonTestCase( String pName ) {
super( pName );
log.debug( "create test case with name: " + pName );
}
// Public --------------------------------------------------------
/**
* Checks if the environment of test is working
**/
public void testEnvironment()
throws Exception
{
// Get all session home interfaces first
log.debug( "testEnvironment(), start" );
BankSessionHome lBankHome = (BankSessionHome) mContext.lookup( BankSessionHome.JNDI_NAME );
CustomerSessionHome lCustomerHome = (CustomerSessionHome) mContext.lookup( CustomerSessionHome.JNDI_NAME );
AccountSessionHome lAccountHome = (AccountSessionHome) mContext.lookup( AccountSessionHome.JNDI_NAME );
TellerSessionHome lTellerHome = (TellerSessionHome) mContext.lookup( TellerSessionHome.JNDI_NAME );
// Create a session bean by default
BankSession lBank = lBankHome.create();
CustomerSession lCustomer = lCustomerHome.create();
AccountSession lAccount = lAccountHome.create();
TellerSession lTeller = lTellerHome.create();
// Create a bank the root of everything
BankData lBankData = lBank.createBank( "Andy's TestBank", "12345 XMass Avenue, New JBoss, GA" );
// Check all the methods on teller inteface because that
// is what we are here working with primarly
CustomerData lCustomerData = lTeller.createCustomer( lBankData.getId(), "One", 100 );
CustomerData lCustomerData2 = lTeller.getCustomer( lCustomerData.getId() );
Collection lCustomers = lTeller.getCustomers( lBankData.getId() );
AccountData lAccountData = lTeller.createAccount( lCustomerData.getId(), Constants.SAVING, 150 );
AccountData lAccountData2 = lTeller.getAccount( lCustomerData.getId(), Constants.SAVING );
AccountData lAccountData3 = lTeller.getAccount( lAccountData.getId() );
AccountData lAccountData4 = lTeller.getAccount( lCustomerData.getId(), Constants.CHECKING );
Collection lAccounts = lTeller.getAccounts( lCustomerData.getId() );
lTeller.deposit( lAccountData4.getId(), 75 );
lTeller.withdraw( lAccountData3.getId(), 63 );
lTeller.transfer( lAccountData4.getId(), lAccountData3.getId(), 52 );
lTeller.removeAccount( lAccountData4.getId() );
lTeller.removeAccount( lAccountData.getId() );
lTeller.removeCustomer( lCustomerData.getId() );
lBank.removeBank( lBankData.getId() );
log.debug( "testEnvironment() ends" );
}
/**
* Marathon test which will:
* - set up test environment (creating bank, customers and accounts)
* - create test thread
* - create or lookup a customer
* - create or look up the accounts
* - do (a list of):
* - withdraw
* - transfer (within customer's accounts or with other customer)
* - deposit
* - maybe the account is deleted
* - maybe the customer is deleted
* - maybe the transactions are listed
*
* A thread represents a teller (either person (can create and delete
* customer and accounts or a electronic one) which loops other many
* business interactions, each loop represents a single business inter-
* action. Therefore each task will sleep and each business interaction
* as well. Currently this is complete random but it would be also possible
* to test crunch times by applying a trend
**/
public void testMarathon()
throws Exception
{
log.debug( "testMarathon(), start" );
// Clean Up Environment
setUp();
// Setup the environment
setupBank();
mCount = getThreadCount();
mExit = false;
log.debug( "testMarathon(), start marathon, " + getThreadCount() + " threads" );
long lStart = System.currentTimeMillis();
for( int i = 0; i < getThreadCount(); i++ ) {
Thread.sleep( 100 );
log.debug( "testMarathon(), create new thread #: " + i );
new Thread(
new RegularTeller( i )
).start();
if( mException != null ) {
// If exception occurrs during thread creation stop it
break;
}
}
// To end the test when an exception is thrown sleep in 'One Minute'
// junks to check in between if an exception is thrown. If yes then
// exit the wait loop and wait for all threads to be ended
int lMinutes = (int) ( getDuration() / ( Constants.ONE_MINUTE ) ) +
( getDuration() % ( Constants.ONE_MINUTE ) == 0 ? 0 : 1 ); // Add one minute if it is split minute
log.debug( "Test runs for: " + lMinutes + " minutes" );
// Loop over a minute period sleep time to ensure
// that when an exception is thrown this threads
// terminate itself, too.
for( int i = 0; i < lMinutes; i++ ) {
log.debug( "Sleep for one minute" );
Thread.sleep( Constants.ONE_MINUTE );
log.debug( "------------------------------------------------------------------------" );
log.debug( "Awake after a sleep for one minute ( " + ( lMinutes - i ) + " minutes left" );
log.debug( "------------------------------------------------------------------------" );
if( mException != null || mCount == 0 ) {
// Exception found then terminate test immediately
break;
}
}
mExit = true; // Switch flag to exit all running threads
while( mCount > 0 ) { // Check until all threads exited
log.debug( "testMarathon(), thread count: " + mCount + ", release lock" );
Thread.sleep( Constants.ONE_SECOND ); // Wait another second to for all threads to exit
}
long lEnd = System.currentTimeMillis();
log.info( "testMarathon(), time balance" );
log.info(
"testMarathon(), total time test was running: " +
( ( lEnd - lStart ) / Constants.ONE_MINUTE ) + " minutes."
);
log.debug( "testMarathon(), ends" );
if( mException != null ) {
// Throw exception if one occurred (the last one occurred)
throw mException;
}
}
protected void setUp()
throws Exception
{
log.debug( "setUp(), start" );
mContext = new InitialContext();
log.info("Remove accounts and customers");
BankSessionHome lBankHome = (BankSessionHome) mContext.lookup( BankSessionHome.JNDI_NAME );
CustomerSessionHome lCustomerHome = (CustomerSessionHome) mContext.lookup( CustomerSessionHome.JNDI_NAME );
AccountSessionHome lAccountHome = (AccountSessionHome) mContext.lookup( AccountSessionHome.JNDI_NAME );
BankSession lBankSession = lBankHome.create();
Collection lBanks = lBankSession.getBanks();
Iterator i = lBanks.iterator();
while( i.hasNext() ) {
BankData lBank = (BankData) i.next();
// Get all customers
CustomerSession lCustomerSession = lCustomerHome.create();
Collection lCustomers = lCustomerSession.getCustomers( lBank.getId() );
Iterator j = lCustomers.iterator();
while( j.hasNext() ) {
CustomerData lCustomer = (CustomerData) j.next();
// Get all accounts
AccountSession lAccountSession = lAccountHome.create();
Collection lAccounts = lAccountSession.getAccounts( lCustomer.getId() );
Iterator k = lAccounts.iterator();
while( k.hasNext() ) {
AccountData lAccount = (AccountData) k.next();
lAccountSession.removeAccount( lAccount.getId() );
}
lCustomerSession.removeCustomer( lCustomer.getId() );
}
lBankSession.removeBank( lBank.getId() );
}
log.debug( "setUp() ends" );
}
protected int getDuration() {
return Integer.getInteger(
"jbosstest.duration",
DEFAULT_DURATION
).intValue();
}
public void setupBank()
throws Exception
{
//AS ToDo
log.debug( "setupBank(), create bank" );
BankData lBank = getBankSession().createBank( "Andy's TestBank", "12345 XMass Avenue, New JBoss, GA" );
for( int i = 0; i < INITIAL_NUMBER_OF_CUSTOMERS; i++ ) {
log.debug( "setupBank(), create customer #: " + i );
CustomerData lCustomer = getCustomerSession().createCustomer( lBank.getId(), "test", 100 );
}
}
private BankSession getBankSession()
throws CreateException, RemoteException, NamingException
{
if( mBankSession == null ) {
mBankSession = ( (BankSessionHome) mContext.lookup( BankSessionHome.JNDI_NAME ) ).create();
}
return mBankSession;
}
private CustomerSession getCustomerSession()
throws CreateException, RemoteException, NamingException
{
return ( (CustomerSessionHome) mContext.lookup( CustomerSessionHome.JNDI_NAME ) ).create();
}
private AccountSession getAccountSession()
throws CreateException, RemoteException, NamingException
{
return ( (AccountSessionHome) mContext.lookup( AccountSessionHome.JNDI_NAME ) ).create();
}
class RegularTeller
implements Runnable
{
private int mId = 0;
private Logger mLog = null;
public RegularTeller( int pId ) {
mId = pId;
mLog = Logger.getLogger( this.getClass().getName() );
}
public void run() {
int mAccountCount = 0;
Random lRandom = new Random();
mLog.debug( "run(), id: " + mId + ", exit: " + mExit );
try {
// Let this thread sleep because the next person must ready first
// This is also here to avoid that the threads start at the same time
Thread.sleep( lRandom.nextInt( 2 * Constants.ONE_MINUTE ) );
// Get bank first
Collection lBanks = getBankSession().getBanks();
BankData lBank = (BankData) lBanks.iterator().next();
mLog.debug( "run(), tread id: " + mId + ", got bank: " + lBank );
// Loop of business interactions
while( !mExit && mException == null ) {
CustomerData lCustomer = null;
mLog.debug( "run(), tread id: " + mId + ", create or find customer" );
if( lRandom.nextInt( 100 ) < 10 ) {
mLog.debug( "run(), thread id: " + mId + ", create new customer" );
lCustomer = getCustomerSession().createCustomer( lBank.getId(), "test", 100 );
mCustomerCount++;
mLog.debug( "run(), thread id: " + mId + ", new customer: " + lCustomer );
} else {
int i = 0;
while( lCustomer == null ) {
i++;
try {
int lCustomerId = lRandom.nextInt( mCustomerCount );
mLog.debug( "run(), thread id: " + mId + ", look up customer, id: " + lCustomerId );
lCustomer = getBankSession().getCustomer( "" + lCustomerId );
mLog.debug( "run(), thread id: " + mId + ", found customer: " + lCustomer );
}
catch( FinderException fe ) {
if( i > 100 ) {
throw fe;
}
}
}
}
mLog.debug( "run(), tread id: " + mId + ", create or find account" );
// Get accounts and decide if to create a new account
List lAccounts = (List) getCustomerSession().getAccounts( lCustomer.getId() );
AccountData lAccount = null;
if( lRandom.nextInt( 100 ) < 5 ) {
try {
lAccount = getCustomerSession().createAccount(
lCustomer.getId(), lRandom.nextInt( 3 ), 123
);
mLog.debug( "run(), thread id: " + mId + ", created account: " + lAccount );
}
catch( CreateException ce ) {
}
}
if( lAccount == null ) {
lAccount = (AccountData) lAccounts.get( lRandom.nextInt( lAccounts.size() ) );
mLog.debug( "run(), thread id: " + mId + ", got account: " + lAccount );
}
if( lAccount == null ) {
throw new RuntimeException( "Could not find an account" );
}
// Do some business methods
int lLoops = lRandom.nextInt( 10 );
for( int i = 0; i < lLoops; i++ ) {
int lSelection = lRandom.nextInt( 4 );
mLog.debug( "run(), thread: " + mId + ", business selection : " + lSelection );
switch( lSelection ) {
case 0:
// Withdraw money when balance is greater than 50
if( lAccount.getBalance() > 50 ) {
getAccountSession().withdraw( lAccount.getId(), lRandom.nextInt( 50 ) );
}
break;
case 1:
if( lAccounts.size() > 1 && lAccount.getBalance() > 50 ) {
AccountData lOtherAccount = null;
while( true ) {
lOtherAccount = (AccountData) lAccounts.get( lRandom.nextInt( lAccounts.size() ) );
if( lOtherAccount.getType() != lAccount.getType() ) {
// Found another account type
break;
}
}
while( true ) {
try {
getAccountSession().transfer( lAccount.getId(), lOtherAccount.getId(), lRandom.nextInt( 50 ) );
break;
}
catch( ServerException se ) {
checkServerException( se );
}
}
}
break;
case 2:
if( lAccount.getBalance() > 50 ) {
List lCustomers = (List) getBankSession().getCustomers( lBank.getId() );
if( lCustomers.size() > 1 ) {
CustomerData lOtherCustomer = null;
while( true ) {
lOtherCustomer = (CustomerData) lCustomers.get( lRandom.nextInt( lCustomers.size() ) );
if( !lOtherCustomer.getId().equals( lCustomer.getId() ) ) {
break;
}
}
List lAccounts2 = (List) getAccountSession().getAccounts( lOtherCustomer.getId() );
if( lAccounts2.size() > 0 ) {
AccountData lOtherAccount = (AccountData) lAccounts2.get( lRandom.nextInt( lAccounts2.size() ) );
while( true ) {
try {
getAccountSession().transfer( lAccount.getId(), lOtherAccount.getId(), lRandom.nextInt( 50 ) );
break;
}
catch( ServerException se ) {
checkServerException( se );
}
}
}
}
}
break;
case 3:
getAccountSession().deposit( lAccount.getId(), lRandom.nextInt( 100 ) );
break;
}
mLog.debug( "run(), thread: " + mId + ", end business iteration, exit: " + mExit );
// Check to see if to exit before sleeping
if( mException != null || mExit ) {
break;
}
// Let this thread sleep because a person cannot work at light speed
Thread.sleep( lRandom.nextInt( 2 * Constants.ONE_MINUTE ) );
// Check to see if to exit before starting another loop of business interactions
if( mException != null || mExit ) {
break;
}
}
}
}
catch( Exception e ) {
mLog.error( "run(), got exception", e );
// Preserve the first exception
if( mException == null ) {
mException = e;
// Terminate all other threads as well
mExit = true;
}
}
mCount--;
mLog.debug( "run(), thread exists, only " + mCount + " active threads left" );
}
private void checkServerException( ServerException pException )
throws ServerException
{
Throwable lThrowable = pException.detail;
if( lThrowable instanceof ApplicationDeadlockException ) {
mLog.debug( "Found ADE in ServerException: " + pException );
return;
} else
if( lThrowable instanceof TransactionRolledbackException ) {
TransactionRolledbackException lTRE = (TransactionRolledbackException) lThrowable;
if( lTRE.detail instanceof ApplicationDeadlockException ) {
mLog.debug( "Found ADE in TransactionRolledbackException: " + lTRE );
return;
} else
if( lTRE.detail instanceof TransactionRolledbackException ) {
TransactionRolledbackException lTRE2 = (TransactionRolledbackException) lTRE.detail;
if( lTRE2.detail instanceof ApplicationDeadlockException ) {
mLog.debug( "Found ADE in 2. TransactionRolledbackException: " + lTRE2 );
return;
}
}
}
throw pException;
}
}
}