package com.yiqiniu.easytrans.test;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;
import com.yiqiniu.easytrans.ConsistentGuardian;
import com.yiqiniu.easytrans.config.EasyTransConifg;
import com.yiqiniu.easytrans.log.TransactionLogReader;
import com.yiqiniu.easytrans.log.vo.LogCollection;
import com.yiqiniu.easytrans.protocol.BusinessIdentifer;
import com.yiqiniu.easytrans.protocol.IdempotentTypeDeclare.TransactionId;
import com.yiqiniu.easytrans.rpc.EasyTransRpcConsumer;
import com.yiqiniu.easytrans.test.mockservice.accounting.AccountingService;
import com.yiqiniu.easytrans.test.mockservice.accounting.easytrans.AccountingCpsMethod.AccountingRequest;
import com.yiqiniu.easytrans.test.mockservice.express.ExpressService;
import com.yiqiniu.easytrans.test.mockservice.express.easytrans.ExpressDeliverAfterTransMethod.ExpressDeliverAfterTransMethodRequest;
import com.yiqiniu.easytrans.test.mockservice.order.NotReliableOrderMessage;
import com.yiqiniu.easytrans.test.mockservice.order.OrderMessage;
import com.yiqiniu.easytrans.test.mockservice.order.OrderService;
import com.yiqiniu.easytrans.test.mockservice.point.PointService;
import com.yiqiniu.easytrans.test.mockservice.wallet.WalletService;
import com.yiqiniu.easytrans.test.mockservice.wallet.easytrans.WalletPayTccMethod.WalletPayTccMethodRequest;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:test-ApplicationContext.xml" })
public class FullTest extends AbstractJUnit4SpringContextTests {
@Resource(name="wholeJdbcTemplate")
private JdbcTemplate wholeJdbcTemplate;
@Resource
OrderService orderService;
@Resource
private ConsistentGuardian guardian;
@Resource
private TransactionLogReader logReader;
@Resource
private EasyTransConifg config;
@Resource
private EasyTransRpcConsumer consumer;
@Resource
private AccountingService accountingService;
@Resource
private ExpressService expressService;
@Resource
private PointService pointService;
@Resource
private WalletService walletService;
private ExecutorService executor = Executors.newFixedThreadPool(4);
// private ExecutorService executor = Executors.newFixedThreadPool(1);
private Logger LOG = LoggerFactory.getLogger(this.getClass());
private int concurrentTestId = 100000;
@Test
public void test(){
try {
//synchronizer test
cleanAndSetUp();
commitedAndSubTransSuccess();
rollbackWithExceptionJustBeforeCommit();
rollbackWithExceptionInMiddle();
rollbackWithExceptionJustAfterStartEasyTrans();
//wait for asynchronous operation
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//consistent guardian test
commitWithExceptionInMiddleOfConsistenGuardian();
rollbackWithExceptionInMiddleOfConsistenGuardian();
//idempotent test
activateThreadPool();
sameMethodConcurrentTcc();
differentMethodConcurrentCompensable();
//execute consistent guardian in case of timeout
List<LogCollection> unfinishedLogs = logReader.getUnfinishedLogs(null, 100, new Date());
for(LogCollection logCollection:unfinishedLogs){
guardian.process(logCollection);
}
//check execute result
Assert.isTrue(accountingService.getTotalCost(1) == 2000);
Assert.isTrue(expressService.getUserExpressCount(1) == 2);
Assert.isTrue(orderService.getUserOrderCount(1) == 2);
Assert.isTrue(pointService.getUserPoint(1) == 2000);
Assert.isTrue(walletService.getUserTotalAmount(1) == 8000);
Assert.isTrue(walletService.getUserFreezeAmount(1) == 0);
//check single
executeTccOnly();
executeCompensableOnly();
executeAfterTransMethodOnly();
executeReliableMsgOnly();
executeNotReliableMessageOnly();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Assert.isTrue(walletService.getUserTotalAmount(1) == 7000);
Assert.isTrue(walletService.getUserFreezeAmount(1) == 0);
Assert.isTrue(accountingService.getTotalCost(1) == 3000);
Assert.isTrue(expressService.getUserExpressCount(1) == 3);
Assert.isTrue(pointService.getUserPoint(1) == 3000);
System.out.println("Test Passed!");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Finish!");
}
private void executeNotReliableMessageOnly() {
OrderService.setNotExecuteBusiness(AccountingRequest.class);
OrderService.setNotExecuteBusiness(ExpressDeliverAfterTransMethodRequest.class);
// OrderService.setNotExecuteBusiness(NotReliableOrderMessage.class);
OrderService.setNotExecuteBusiness(OrderMessage.class);
OrderService.setNotExecuteBusiness(WalletPayTccMethodRequest.class);
orderService.buySomething(1, 1000);
OrderService.clearNotExecuteSet();
}
private void executeAfterTransMethodOnly() {
OrderService.setNotExecuteBusiness(AccountingRequest.class);
// OrderService.setNotExecuteBusiness(ExpressDeliverAfterTransMethodRequest.class);
OrderService.setNotExecuteBusiness(NotReliableOrderMessage.class);
OrderService.setNotExecuteBusiness(OrderMessage.class);
OrderService.setNotExecuteBusiness(WalletPayTccMethodRequest.class);
orderService.buySomething(1, 1000);
OrderService.clearNotExecuteSet();
}
private void executeReliableMsgOnly() {
OrderService.setNotExecuteBusiness(AccountingRequest.class);
OrderService.setNotExecuteBusiness(ExpressDeliverAfterTransMethodRequest.class);
OrderService.setNotExecuteBusiness(NotReliableOrderMessage.class);
// OrderService.setNotExecuteBusiness(OrderMessage.class);
OrderService.setNotExecuteBusiness(WalletPayTccMethodRequest.class);
orderService.buySomething(1, 1000);
OrderService.clearNotExecuteSet();
}
private void executeTccOnly() {
OrderService.setNotExecuteBusiness(AccountingRequest.class);
OrderService.setNotExecuteBusiness(ExpressDeliverAfterTransMethodRequest.class);
OrderService.setNotExecuteBusiness(NotReliableOrderMessage.class);
OrderService.setNotExecuteBusiness(OrderMessage.class);
// OrderService.setNotExecuteBusiness(WalletPayTccMethodRequest.class);
orderService.buySomething(1, 1000);
OrderService.clearNotExecuteSet();
}
private void executeCompensableOnly() {
// OrderService.setNotExecuteBusiness(AccountingRequest.class);
OrderService.setNotExecuteBusiness(ExpressDeliverAfterTransMethodRequest.class);
OrderService.setNotExecuteBusiness(NotReliableOrderMessage.class);
OrderService.setNotExecuteBusiness(OrderMessage.class);
OrderService.setNotExecuteBusiness(WalletPayTccMethodRequest.class);
orderService.buySomething(1, 1000);
OrderService.clearNotExecuteSet();
}
private void differentMethodConcurrentCompensable() {
final BusinessIdentifer annotation = AccountingRequest.class.getAnnotation(BusinessIdentifer.class);
final int i = concurrentTestId++;
final AccountingRequest request = new AccountingRequest();
request.setAmount(1000l);
request.setUserId(1);
TransactionId parentTrxId = new TransactionId(config.getAppId(), "concurrentTest", String.valueOf(i));
request.setParentTrxId(parentTrxId);
Callable<Object> doCompensableBusinessRequest = new Callable<Object>() {
@Override
public Object call() throws Exception {
return consumer.call(annotation.appId(), annotation.busCode(), "doCompensableBusiness", request);
}
};
Callable<Object> compensationRequest = new Callable<Object>() {
@Override
public Object call() throws Exception {
return consumer.call(annotation.appId(), annotation.busCode(), "compensation", request);
}
};
List<Callable<Object>> asListTry = Arrays.asList(compensationRequest,doCompensableBusinessRequest,compensationRequest,doCompensableBusinessRequest,compensationRequest,doCompensableBusinessRequest,compensationRequest,doCompensableBusinessRequest);
try {
List<Future<Object>> invokeAll = executor.invokeAll(asListTry);
for(Future<Object> future:invokeAll){
try {
System.out.println(future.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void activateThreadPool() {
Callable<Object> runnable = new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
};
try {
executor.invokeAll(Arrays.asList(runnable,runnable,runnable,runnable,runnable,runnable,runnable,runnable));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void sameMethodConcurrentTcc() {
final BusinessIdentifer annotation = WalletPayTccMethodRequest.class.getAnnotation(BusinessIdentifer.class);
final int i = concurrentTestId++;
final WalletPayTccMethodRequest request = new WalletPayTccMethodRequest();
request.setPayAmount(1000l);
request.setUserId(1);
TransactionId parentTrxId = new TransactionId(config.getAppId(), "concurrentTest", String.valueOf(i));
request.setParentTrxId(parentTrxId);
Callable<Object> tryRequest = new Callable<Object>() {
@Override
public Object call() throws Exception {
return consumer.call(annotation.appId(), annotation.busCode(), "doTry", request);
}
};
Callable<Object> cancelRequest = new Callable<Object>() {
@Override
public Object call() throws Exception {
return consumer.call(annotation.appId(), annotation.busCode(), "doCancel", request);
}
};
List<Callable<Object>> asListTry = Arrays.asList(tryRequest,tryRequest,tryRequest,tryRequest,tryRequest,tryRequest,tryRequest,tryRequest);
try {
List<Future<Object>> invokeAll = executor.invokeAll(asListTry);
for(Future<Object> future:invokeAll){
try {
System.out.println(future.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
List<Callable<Object>> asListCancel = Arrays.asList(cancelRequest,cancelRequest,cancelRequest,cancelRequest,cancelRequest,cancelRequest,cancelRequest,cancelRequest);
try {
List<Future<Object>> invokeAll = executor.invokeAll(asListCancel);
for(Future<Object> future:invokeAll){
try {
System.out.println(future.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void rollbackWithExceptionInMiddleOfConsistenGuardian() {
try {
OrderService.setExceptionTag(OrderService.EXCEPTION_TAG_BEFORE_COMMIT);
OrderService.setExceptionTag(OrderService.EXCEPTION_TAG_IN_MIDDLE_OF_CONSISTENT_GUARDIAN_WITH_ROLLEDBACK_MASTER_TRANS);
orderService.buySomething(1, 1000);
} catch (Exception e) {
LOG.info("",e);
}
try {
Thread.sleep(1000);//wait for asynchronous operation
} catch (InterruptedException e) {
e.printStackTrace();
}
OrderService.clearExceptionSet();
List<LogCollection> unfinishedLogs = logReader.getUnfinishedLogs(null, 1, new Date());
LogCollection logCollection = unfinishedLogs.get(0);
guardian.process(logCollection);
}
private void commitWithExceptionInMiddleOfConsistenGuardian() {
try {
OrderService.setExceptionTag(OrderService.EXCEPTION_TAG_IN_MIDDLE_OF_CONSISTENT_GUARDIAN_WITH_SUCCESS_MASTER_TRANS);
orderService.buySomething(1, 1000);
} catch (Exception e) {
LOG.info("",e);
}
try {
Thread.sleep(1000);//wait for asynchronous operation
} catch (InterruptedException e) {
e.printStackTrace();
}
OrderService.clearExceptionSet();
List<LogCollection> unfinishedLogs = logReader.getUnfinishedLogs(null, 1, new Date());
LogCollection logCollection = unfinishedLogs.get(0);
guardian.process(logCollection);
}
private void cleanAndSetUp() {
wholeJdbcTemplate.batchUpdate(new String[]{
"Create Table If Not Exists `order` ( `order_id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `money` bigint(20) NOT NULL, `create_time` datetime NOT NULL, PRIMARY KEY (`order_id`)) DEFAULT CHARSET=utf8",
"TRUNCATE `order`",
"Create Table If Not Exists `wallet` ( `user_id` int(11) NOT NULL, `total_amount` bigint(20) NOT NULL, `freeze_amount` bigint(20) NOT NULL, PRIMARY KEY (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8",
"TRUNCATE `wallet`",
"Create Table If Not Exists `accounting` ( `accounting_id` int(11) NOT NULL AUTO_INCREMENT, `p_app_id` varchar(32) NOT NULL, `p_bus_code` varchar(128) NOT NULL, `p_trx_id` varchar(64) NOT NULL, `user_id` int(11) NOT NULL, `amount` bigint(20) NOT NULL, `create_time` datetime NOT NULL, PRIMARY KEY (`accounting_id`)) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8",
"TRUNCATE `accounting`",
"Create Table If Not Exists `point` ( `user_id` int(11) NOT NULL, `point` bigint(20) NOT NULL, PRIMARY KEY (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8",
"TRUNCATE `point`",
"Create Table If Not Exists `express` ( `p_app_id` varchar(32) NOT NULL, `p_bus_code` varchar(128) NOT NULL, `p_trx_id` varchar(64) NOT NULL, `user_id` int(11) NOT NULL, PRIMARY KEY (`p_app_id`,`p_bus_code`,`p_trx_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8",
"TRUNCATE `express`",
"TRUNCATE `trans_log_unfinished`",
"TRUNCATE `trans_log_detail`",
"TRUNCATE `executed_trans`",
"TRUNCATE `idempotent`",
"INSERT INTO `wallet` (`user_id`, `total_amount`, `freeze_amount`) VALUES ('1', '10000', '0')",
"INSERT INTO `point` (`user_id`, `point`) VALUES ('1', '0')"
});
}
public void commitedAndSubTransSuccess(){
orderService.buySomething(1, 1000);
}
public void rollbackWithExceptionJustBeforeCommit(){
try {
OrderService.setExceptionTag(OrderService.EXCEPTION_TAG_BEFORE_COMMIT);
orderService.buySomething(1, 1000);
} catch (Exception e) {
LOG.info("",e);
}
OrderService.clearExceptionSet();
}
public void rollbackWithExceptionInMiddle(){
try {
OrderService.setExceptionTag(OrderService.EXCEPTION_TAG_IN_THE_MIDDLE);
orderService.buySomething(1, 1000);
} catch (Exception e) {
LOG.info("",e);
}
OrderService.clearExceptionSet();
}
public void rollbackWithExceptionJustAfterStartEasyTrans(){
try {
OrderService.setExceptionTag(OrderService.EXCEPTION_TAG_JUST_AFTER_START_EASY_TRANSACTION);
orderService.buySomething(1, 1000);
} catch (Exception e) {
LOG.info("",e);
}
OrderService.clearExceptionSet();
}
}