| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- package core
- import (
- "fmt"
- "math/rand"
- "path/filepath"
- "testing"
- "git.ycl.pub/yl32940/TsDB2/DB/core/page"
- "git.ycl.pub/yl32940/TsDB2/DB/core/types"
- )
- // TestRandomOpsAgainstModel 模糊测试: 随机插入/更新/删除 (含整表删空再灌),
- // 每步与一个内存模型比对; 最后关库重开, 全量校验持久化结果。
- // 固定种子 → 失败可复现。这是对抗"人工审计想不到的序列"的主要手段。
- func TestRandomOpsAgainstModel(t *testing.T) {
- seeds := []int64{1, 42, 1337, 20240927, 777, 90091}
- for _, seed := range seeds {
- t.Run(fmt.Sprintf("seed-%d", seed), func(t *testing.T) {
- path := filepath.Join(t.TempDir(), "fuzz.db")
- db, err := Open(path)
- if err != nil {
- t.Fatalf("open: %v", err)
- }
- db.SetAutoCommitDelay(0)
- tbl, err := db.CreateTable("t", []types.FieldInfo{
- {FieldType: types.FieldType_Uint64, FieldName: []byte("id")},
- {FieldType: types.FieldType_Int64, FieldName: []byte("a")},
- {FieldType: types.FieldType_Int64, FieldName: []byte("b")},
- })
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
- t.Fatalf("建主键索引: %v", err)
- }
- if _, err := tbl.CreateIndex("idx_a", page.IndexTypeBTree, []byte{1}); err != nil {
- t.Fatalf("建二级索引: %v", err)
- }
- rng := rand.New(rand.NewSource(seed))
- model := map[uint64][2]int64{}
- ids := []uint64{} // 存活 id 池 (用于随机改/删)
- nextID := uint64(1)
- insert := func(id uint64, a, b int64) {
- if _, err := tbl.InsertRowWithID(id, []types.FieldValue{
- {FieldName: []byte("id"), Value: id},
- {FieldName: []byte("a"), Value: a},
- {FieldName: []byte("b"), Value: b},
- }); err != nil {
- t.Fatalf("插入 id=%d: %v", id, err)
- }
- }
- remove := func(id uint64) {
- if err := tbl.DeleteRowByPK(id); err != nil {
- t.Fatalf("删除 id=%d: %v", id, err)
- }
- }
- for op := 0; op < 8000; op++ {
- roll := rng.Intn(100)
- switch {
- case roll < 34: // 插入
- id := nextID
- nextID++
- a := int64(rng.Intn(20))
- b := int64(rng.Intn(7))
- insert(id, a, b)
- model[id] = [2]int64{a, b}
- ids = append(ids, id)
- case roll < 62 && len(ids) > 0: // 更新 (整行覆盖)
- k := rng.Intn(len(ids))
- id := ids[k]
- a := int64(rng.Intn(20))
- b := int64(rng.Intn(7))
- if err := tbl.UpdateRowByPK(id, []types.FieldValue{
- {FieldName: []byte("a"), Value: a},
- {FieldName: []byte("b"), Value: b},
- }); err != nil {
- t.Fatalf("更新 id=%d: %v", id, err)
- }
- model[id] = [2]int64{a, b}
- case roll < 88 && len(ids) > 0: // 删除
- k := rng.Intn(len(ids))
- id := ids[k]
- ids = append(ids[:k], ids[k+1:]...)
- remove(id)
- delete(model, id)
- case roll < 90: // 整表删空 (制造删空页 + 空闲页复用)
- for id := range model {
- remove(id)
- }
- model = map[uint64][2]int64{}
- ids = nil
- default: // 提交
- if err := db.Commit(); err != nil {
- t.Fatalf("commit: %v", err)
- }
- }
- }
- if err := db.Commit(); err != nil {
- t.Fatalf("final commit: %v", err)
- }
- verify := func(phase string) {
- // 行数
- if c, _ := tbl.Query().Count(); c != int64(len(model)) {
- t.Fatalf("%s: count=%d, 模型 %d", phase, c, len(model))
- }
- // 全量逐行比对
- seen := map[uint64][2]int64{}
- err := tbl.Query().ScanWithPKID(func(pkID uint64, row []types.FieldValue) bool {
- var pair [2]int64
- for _, fv := range row {
- switch string(fv.FieldName) {
- case "a":
- pair[0] = fv.Value.(int64)
- case "b":
- pair[1] = fv.Value.(int64)
- }
- }
- seen[pkID] = pair
- return true
- })
- if err != nil {
- t.Fatalf("%s: scan: %v", phase, err)
- }
- if len(seen) != len(model) {
- t.Fatalf("%s: 扫到 %d 行, 模型 %d 行", phase, len(seen), len(model))
- }
- for id, want := range model {
- got, ok := seen[id]
- if !ok || got != want {
- t.Fatalf("%s: id=%d 得到 %+v (存在=%v), 模型 %+v", phase, id, got, ok, want)
- }
- }
- // 二级索引等值计数与模型交叉验证
- for a := int64(0); a < 20; a++ {
- wantN := int64(0)
- for _, pair := range model {
- if pair[0] == a {
- wantN++
- }
- }
- if n, _ := tbl.Query().Where(Eq("a", a, 0)).Count(); n != wantN {
- t.Fatalf("%s: a=%d 索引 count=%d, 模型 %d", phase, a, n, wantN)
- }
- }
- }
- verify("关库前")
- if err := db.Close(); err != nil {
- t.Fatalf("close: %v", err)
- }
- // 重开: 持久化校验
- db2, err := Open(path)
- if err != nil {
- t.Fatalf("重开: %v", err)
- }
- defer db2.Close()
- tbl2, ok := db2.FindTable("t")
- if !ok {
- t.Fatalf("重开找不到表")
- }
- tbl = tbl2
- verify("重开后")
- })
- }
- }
|