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("重开后") }) } }