/*
* JBoss, Home of Professional Open Source.
* Copyright 2013, Red Hat, Inc., 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 io.narayana.spi;
import com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean;
import com.arjuna.ats.arjuna.recovery.RecoveryManager;
import com.arjuna.ats.jta.common.jtaPropertyManager;
import com.arjuna.common.internal.util.propertyservice.BeanPopulator;
import io.narayana.spi.util.*;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.*;
import static org.junit.Assert.*;
/*
* To test recovery do a run than generates a recovery record:
* ./build.sh -f ArjunaJTA/spi/pom.xml test -Dtest=io.narayana.spi.SPIUnitTest#testXADSWithFaults -Dspitest.fault=halt
* followed by a run that will check it recovered ok (but see the note below about recovery only working with postgresql and
* the need to manually recover h2):
* ./build.sh -f ArjunaJTA/spi/pom.xml test -Dtest=io.narayana.spi.SPIUnitTest#testWaitForRecovery -Dspitest.fault=recover
*
* But you need to have postgresql as one of your databases by editing the file ArjunaJTA/spi/target/test-classes/db.properties
* and including the line DB_PREFIXES=DB_H2,DB_PGSQL at the top of the file. This file must also be modified if you would
* like to test with a different set of databases.
*
* Note that h2 does not support recovery properly so you will need to manually resolve it before running the recovery step:
* first start the H2 console. Eg if you have it installed in /usr/local/h2/bin:
* java -jar /usr/local/h2/bin/h2*.jar &
* this will open a console in your default browser. Log in as user sa/sa
* select * from information_schema.in_doubt
* followed by COMMIT TRANSACTION xid
* now disconnect (since h2 only allows a single user connection)
*/
public class SPIUnitTest
{
private String userTransactionJNDIContext = jtaPropertyManager.getJTAEnvironmentBean().getUserTransactionJNDIContext();
private String transactionManagerJNDIContext = jtaPropertyManager.getJTAEnvironmentBean().getTransactionManagerJNDIContext();
@BeforeClass
public static void setUp() throws Exception {
JndiProvider.start();
JndiProvider.initBindings();
BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).setRecoveryBackoffPeriod(1);
TransactionServiceFactory.start(true);
}
@AfterClass
public static void tearDown() throws Exception {
TransactionServiceFactory.stop();
JndiProvider.stop();
}
@Test
public void testTMRestart() throws Exception {
UserTransaction txn = (UserTransaction) new InitialContext().lookup(userTransactionJNDIContext);
TransactionServiceFactory.stop();
// UserTransaction should still work even thought the TF has been stopped
txn.begin();
txn.commit();
// Validate that JNDI lookups fail when the TF is stopped
try {
new InitialContext().lookup(userTransactionJNDIContext);
fail("User transaction is still bound after stopping the TransactionServiceFactory");
} catch (NamingException e) {
}
// Validate that the TF can be restarted (without a recovery manager)
TransactionServiceFactory.start(false);
testCommitXADS();
TransactionServiceFactory.stop();
// Validate that the TF can be restarted (with a recovery manager)
TransactionServiceFactory.start(true);
testCommitXADS();
}
@Test(expected = RollbackException.class)
public void testTimeout() throws Exception {
final int timeout = 2;
UserTransaction txn = (UserTransaction) new InitialContext().lookup(userTransactionJNDIContext);
txn.setTransactionTimeout(timeout);
txn.begin();
Thread.sleep(timeout * 1000 + 1000);
txn.commit();
fail("committing a timed out transaction should have thrown a RollbackException");
}
@Test
public void testTransaction() throws Exception {
UserTransaction txn = (UserTransaction) new InitialContext().lookup(userTransactionJNDIContext);
txn.begin();
assertNotNull(txn);
assertEquals(Status.STATUS_ACTIVE, txn.getStatus());
txn.commit();
// the transaction should have been disassociated
assertEquals(Status.STATUS_NO_TRANSACTION, txn.getStatus());
txn.begin();
txn.commit();
assertEquals(Status.STATUS_NO_TRANSACTION, txn.getStatus());
}
@Test
public void testCommitXADS() throws Exception {
UserTransaction txn = (UserTransaction) new InitialContext().lookup(userTransactionJNDIContext);
DbTester dbTester = new DbTester();
txn.begin();
dbTester.doInserts();
txn.commit();
dbTester.assertRowCounts(true);
dbTester.dropTables();
}
@Test
public void testAbortXADS() throws Exception {
UserTransaction txn = (UserTransaction) new InitialContext().lookup(userTransactionJNDIContext);
DbTester dbTester = new DbTester();
txn.begin();
dbTester.doInserts();
txn.rollback();
dbTester.assertRowCounts(false);
dbTester.dropTables();
}
@Test
public void testXADSWithFaults() throws Exception {
UserTransaction txn = (UserTransaction) new InitialContext().lookup(userTransactionJNDIContext);
DbTester dbTester = new DbTester();
String fault = dbTester.getFault();
System.out.printf("testXADSWithFaults with fault type %s%n", fault);
txn.begin();
dbTester.doInserts();
try {
txn.commit();
assertFalse("VM should have halted", "halt".equalsIgnoreCase(fault));
dbTester.assertRowCounts(true);
} catch (Exception e) {
if ("XA_RBROLLBACK".equalsIgnoreCase(fault)) {
if (e instanceof HeuristicMixedException) {
System.out.printf("expected exception: " + e.getMessage());
} else if (e instanceof RollbackException) {
System.out.printf("expected exception: " + e.getMessage());
} else {
dbTester.dropTables();
throw e;
}
} else {
System.out.printf("possibly unexpected exception (what fault type did you inject: " + e.getMessage());
}
}
dbTester.dropTables();
}
@Test
public void testWaitForRecovery() throws Exception {
DbTester dbTester = new DbTester(false);
if (!"recover".equalsIgnoreCase(dbTester.getFault()))
return;
dbTester.clearCounts();
System.out.println("testWaitForRecovery: waiting for recovery");
RecoveryManager.manager().scan();
dbTester.assertRowCounts(true);
}
@Test
public void testSynchronization() throws Exception {
TestSynchronization synch = new TestSynchronization() ;
UserTransaction txn = (UserTransaction) new InitialContext().lookup(userTransactionJNDIContext);
TransactionManager transactionManager = (TransactionManager) new InitialContext().lookup(transactionManagerJNDIContext);
txn.begin();
transactionManager.getTransaction().registerSynchronization(synch);
txn.commit();
assertTrue(synch.beforeCalled);
assertTrue(synch.afterCalled);
}
class TestSynchronization implements Synchronization {
boolean beforeCalled = false;
boolean afterCalled = false;
@Override
public void beforeCompletion() {
beforeCalled = true;
}
@Override
public void afterCompletion(int i) {
afterCalled = true;
}
}
}