package fuzz
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"runtime/debug"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"simple-kv-store/internal/client"
|
|
"simple-kv-store/internal/nodes"
|
|
"simple-kv-store/threadTest"
|
|
"strconv"
|
|
)
|
|
|
|
func FuzzRaftBasic(f *testing.F) {
|
|
var seenSeeds sync.Map
|
|
// 添加初始种子
|
|
f.Add(int64(1))
|
|
fmt.Println("Running")
|
|
|
|
f.Fuzz(func(t *testing.T, seed int64) {
|
|
if _, loaded := seenSeeds.LoadOrStore(seed, true); loaded {
|
|
t.Skipf("Seed %d already tested, skipping...", seed)
|
|
return
|
|
}
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
msg := fmt.Sprintf("goroutine panic: %v\n%s", r, debug.Stack())
|
|
f, _ := os.Create("panic_goroutine.log")
|
|
fmt.Fprint(f, msg)
|
|
f.Close()
|
|
}
|
|
}()
|
|
|
|
r := rand.New(rand.NewSource(seed)) // 使用局部 rand
|
|
|
|
n := 3 + 2*(r.Intn(4))
|
|
fmt.Printf("随机了%d个节点\n", n)
|
|
logs := (r.Intn(10))
|
|
fmt.Printf("随机了%d份日志\n", logs)
|
|
var peerIds []string
|
|
for i := 0; i < n; i++ {
|
|
peerIds = append(peerIds, strconv.Itoa(int(seed)) + "." + strconv.Itoa(i+1))
|
|
}
|
|
|
|
ctx := nodes.NewCtx()
|
|
threadTransport := nodes.NewThreadTransport(ctx)
|
|
var quitCollections []chan struct{}
|
|
var nodeCollections []*nodes.Node
|
|
|
|
for i := 0; i < n; i++ {
|
|
node, quitChan := threadTest.ExecuteNodeI(strconv.Itoa(int(seed)) + "." + strconv.Itoa(i+1), false, peerIds, threadTransport)
|
|
nodeCollections = append(nodeCollections, node)
|
|
node.RTTable.SetElectionTimeout(750 * time.Millisecond)
|
|
quitCollections = append(quitCollections, quitChan)
|
|
}
|
|
|
|
// 模拟 a-b 通讯行为
|
|
faultyNodes := injectRandomBehavior(ctx, r, peerIds)
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
clientObj := clientPkg.NewClient("0", peerIds, threadTransport)
|
|
|
|
for i := 0; i < logs; i++ {
|
|
key := fmt.Sprintf("k%d", i)
|
|
log := nodes.LogEntry{Key: key, Value: "v"}
|
|
clientObj.Write(log)
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
var rightNodeCollections []*nodes.Node
|
|
for _, node := range nodeCollections {
|
|
if !faultyNodes[node.SelfId] {
|
|
rightNodeCollections = append(rightNodeCollections, node)
|
|
}
|
|
}
|
|
threadTest.CheckSameLog(t, rightNodeCollections)
|
|
threadTest.CheckZeroOrOneLeader(t, nodeCollections)
|
|
|
|
for _, quitChan := range quitCollections {
|
|
close(quitChan)
|
|
}
|
|
time.Sleep(time.Second)
|
|
for i := 0; i < n; i++ {
|
|
// 确保完成退出
|
|
nodeCollections[i].Mu.Lock()
|
|
if !nodeCollections[i].IsFinish {
|
|
nodeCollections[i].IsFinish = true
|
|
}
|
|
nodeCollections[i].Mu.Unlock()
|
|
|
|
os.RemoveAll("leveldb/simple-kv-store" + strconv.Itoa(int(seed)) + "." + strconv.Itoa(i+1))
|
|
os.RemoveAll("storage/node" + strconv.Itoa(int(seed)) + "." + strconv.Itoa(i+1))
|
|
}
|
|
})
|
|
}
|
|
|
|
// 注入节点间行为
|
|
func injectRandomBehavior(ctx *nodes.Ctx, r *rand.Rand, peers []string) (map[string]bool) {
|
|
behaviors := []nodes.CallBehavior{
|
|
nodes.FailRpc,
|
|
nodes.DelayRpc,
|
|
nodes.RetryRpc,
|
|
}
|
|
n := len(peers)
|
|
maxFaulty := r.Intn(n/2 + 1) // 随机选择 0 ~ n/2 个出问题的节点
|
|
|
|
// 随机选择出问题的节点
|
|
shuffled := append([]string(nil), peers...)
|
|
r.Shuffle(n, func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] })
|
|
faultyNodes := make(map[string]bool)
|
|
for i := 0; i < maxFaulty; i++ {
|
|
faultyNodes[shuffled[i]] = true
|
|
}
|
|
|
|
for _, one := range peers {
|
|
if faultyNodes[one] {
|
|
b := behaviors[r.Intn(len(behaviors))]
|
|
delay := time.Duration(r.Intn(100)) * time.Millisecond
|
|
|
|
switch b {
|
|
case nodes.FailRpc:
|
|
fmt.Printf("[%s]的异常行为是fail\n", one)
|
|
case nodes.DelayRpc:
|
|
fmt.Printf("[%s]的异常行为是delay\n", one)
|
|
case nodes.RetryRpc:
|
|
fmt.Printf("[%s]的异常行为是retry\n", one)
|
|
}
|
|
|
|
for _, two := range peers {
|
|
if one == two {
|
|
continue
|
|
}
|
|
|
|
if faultyNodes[one] && faultyNodes[two] {
|
|
ctx.SetBehavior(one, two, nodes.FailRpc, 0, 0)
|
|
ctx.SetBehavior(one, two, nodes.FailRpc, 0, 0)
|
|
} else {
|
|
ctx.SetBehavior(one, two, b, delay, 2)
|
|
ctx.SetBehavior(two, one, b, delay, 2)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
return faultyNodes
|
|
}
|