🔥A cross-language distributed transaction manager. Support xa, tcc, saga, transactional messages. 跨语言分布式事务管理器
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

134 lines
4.6 KiB

/*
* Copyright (c) 2021 yedf. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
package test
import (
"context"
"database/sql"
"fmt"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
"github.com/stretchr/testify/assert"
"github.com/yedf/dtm/common"
"github.com/yedf/dtm/dtmcli"
"github.com/yedf/dtm/dtmcli/dtmimp"
"github.com/yedf/dtm/examples"
)
func TestTccBarrierNormal(t *testing.T) {
req := examples.GenTransReq(30, false, false)
gid := dtmimp.GetFuncName()
err := dtmcli.TccGlobalTransaction(DtmServer, gid, func(tcc *dtmcli.Tcc) (*resty.Response, error) {
_, err := tcc.CallBranch(req, Busi+"/TccBTransOutTry", Busi+"/TccBTransOutConfirm", Busi+"/TccBTransOutCancel")
assert.Nil(t, err)
return tcc.CallBranch(req, Busi+"/TccBTransInTry", Busi+"/TccBTransInConfirm", Busi+"/TccBTransInCancel")
})
assert.Nil(t, err)
waitTransProcessed(gid)
assert.Equal(t, StatusSucceed, getTransStatus(gid))
assert.Equal(t, []string{StatusPrepared, StatusSucceed, StatusPrepared, StatusSucceed}, getBranchesStatus(gid))
}
func TestTccBarrierRollback(t *testing.T) {
req := examples.GenTransReq(30, false, true)
gid := dtmimp.GetFuncName()
err := dtmcli.TccGlobalTransaction(DtmServer, gid, func(tcc *dtmcli.Tcc) (*resty.Response, error) {
_, err := tcc.CallBranch(req, Busi+"/TccBTransOutTry", Busi+"/TccBTransOutConfirm", Busi+"/TccBTransOutCancel")
assert.Nil(t, err)
return tcc.CallBranch(req, Busi+"/TccBTransInTry", Busi+"/TccBTransInConfirm", Busi+"/TccBTransInCancel")
})
assert.Error(t, err)
waitTransProcessed(gid)
assert.Equal(t, StatusFailed, getTransStatus(gid))
assert.Equal(t, []string{StatusSucceed, StatusPrepared, StatusSucceed, StatusPrepared}, getBranchesStatus(gid))
}
func TestTccBarrierDisorder(t *testing.T) {
timeoutChan := make(chan string, 2)
finishedChan := make(chan string, 2)
gid := dtmimp.GetFuncName()
err := dtmcli.TccGlobalTransaction(DtmServer, gid, func(tcc *dtmcli.Tcc) (*resty.Response, error) {
body := &examples.TransReq{Amount: 30}
tryURL := Busi + "/TccBTransOutTry"
confirmURL := Busi + "/TccBTransOutConfirm"
cancelURL := Busi + "/TccBSleepCancel"
// 请参见子事务屏障里的时序图,这里为了模拟该时序图,手动拆解了callbranch
branchID := tcc.NewSubBranchID()
sleeped := false
app.POST(examples.BusiAPI+"/TccBSleepCancel", common.WrapHandler(func(c *gin.Context) (interface{}, error) {
res, err := examples.TccBarrierTransOutCancel(c)
if !sleeped {
sleeped = true
dtmimp.Logf("sleep before cancel return")
<-timeoutChan
finishedChan <- "1"
}
return res, err
}))
// 注册子事务
resp, err := dtmimp.RestyClient.R().
SetBody(map[string]interface{}{
"gid": tcc.Gid,
"branch_id": branchID,
"trans_type": "tcc",
"status": StatusPrepared,
"data": string(dtmimp.MustMarshal(body)),
dtmcli.BranchConfirm: confirmURL,
dtmcli.BranchCancel: cancelURL,
}).Post(fmt.Sprintf("%s/%s", tcc.Dtm, "registerBranch"))
assert.Nil(t, err)
assert.Contains(t, resp.String(), dtmcli.ResultSuccess)
go func() {
dtmimp.Logf("sleeping to wait for tcc try timeout")
<-timeoutChan
r, _ := dtmimp.RestyClient.R().
SetBody(body).
SetQueryParams(map[string]string{
"dtm": tcc.Dtm,
"gid": tcc.Gid,
"branch_id": branchID,
"trans_type": "tcc",
"op": dtmcli.BranchTry,
}).
Post(tryURL)
assert.True(t, strings.Contains(r.String(), dtmcli.ResultSuccess)) // 这个是悬挂操作,为了简单起见,依旧让他返回成功
finishedChan <- "1"
}()
dtmimp.Logf("cron to timeout and then call cancel")
go cronTransOnceForwardNow(300)
time.Sleep(100 * time.Millisecond)
dtmimp.Logf("cron to timeout and then call cancelled twice")
cronTransOnceForwardNow(300)
timeoutChan <- "wake"
timeoutChan <- "wake"
<-finishedChan
<-finishedChan
time.Sleep(100 * time.Millisecond)
return nil, fmt.Errorf("a cancelled tcc")
})
assert.Error(t, err, fmt.Errorf("a cancelled tcc"))
assert.Equal(t, []string{StatusSucceed, StatusPrepared}, getBranchesStatus(gid))
assert.Equal(t, StatusFailed, getTransStatus(gid))
}
func TestTccBarrierPanic(t *testing.T) {
bb := &dtmcli.BranchBarrier{TransType: "saga", Gid: "gid1", BranchID: "bid1", Op: "action", BarrierID: 1}
var err error
func() {
defer dtmimp.P2E(&err)
tx, _ := dbGet().ToSQLDB().BeginTx(context.Background(), &sql.TxOptions{})
bb.Call(tx, func(tx *sql.Tx) error {
panic(fmt.Errorf("an error"))
})
}()
assert.Error(t, err, fmt.Errorf("an error"))
}